348 lines
9.8 KiB
TypeScript
348 lines
9.8 KiB
TypeScript
'use server';
|
|
|
|
import { createClient } from '@/app/_utils/supabase/client';
|
|
|
|
import {
|
|
ICrimes,
|
|
ICrimesByYearAndMonth,
|
|
IDistanceResult,
|
|
} from '@/app/_utils/types/crimes';
|
|
import { getInjection } from '@/di/container';
|
|
import db from '@/prisma/db';
|
|
import {
|
|
AuthenticationError,
|
|
UnauthenticatedError,
|
|
} from '@/src/entities/errors/auth';
|
|
import { InputParseError } from '@/src/entities/errors/common';
|
|
|
|
export async function getAvailableYears() {
|
|
const instrumentationService = getInjection('IInstrumentationService');
|
|
return await instrumentationService.instrumentServerAction(
|
|
'Available Years',
|
|
{ recordResponse: true },
|
|
async () => {
|
|
try {
|
|
const years = await db.crimes.findMany({
|
|
select: {
|
|
year: true,
|
|
},
|
|
distinct: ['year'],
|
|
orderBy: {
|
|
year: 'asc',
|
|
},
|
|
});
|
|
|
|
return years.map((year) => year.year);
|
|
} catch (err) {
|
|
if (err instanceof InputParseError) {
|
|
// return {
|
|
// error: err.message,
|
|
// };
|
|
|
|
throw new InputParseError(err.message);
|
|
}
|
|
|
|
if (err instanceof AuthenticationError) {
|
|
// return {
|
|
// error: 'User not found.',
|
|
// };
|
|
|
|
throw new AuthenticationError(
|
|
'There was an error with the credentials. Please try again or contact support.'
|
|
);
|
|
}
|
|
|
|
const crashReporterService = getInjection('ICrashReporterService');
|
|
crashReporterService.report(err);
|
|
// return {
|
|
// error:
|
|
// 'An error happened. The developers have been notified. Please try again later.',
|
|
// };
|
|
throw new Error(
|
|
'An error happened. The developers have been notified. Please try again later.'
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getCrimeCategories() {
|
|
const instrumentationService = getInjection('IInstrumentationService');
|
|
return await instrumentationService.instrumentServerAction(
|
|
'Crime Categories',
|
|
{ recordResponse: true },
|
|
async () => {
|
|
try {
|
|
const categories = await db.crime_categories.findMany({
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
type: true,
|
|
},
|
|
});
|
|
|
|
return categories;
|
|
} catch (err) {
|
|
if (err instanceof InputParseError) {
|
|
// return {
|
|
// error: err.message,
|
|
// };
|
|
|
|
throw new InputParseError(err.message);
|
|
}
|
|
|
|
if (err instanceof AuthenticationError) {
|
|
// return {
|
|
// error: 'User not found.',
|
|
// };
|
|
|
|
throw new AuthenticationError(
|
|
'There was an error with the credentials. Please try again or contact support.'
|
|
);
|
|
}
|
|
|
|
const crashReporterService = getInjection('ICrashReporterService');
|
|
crashReporterService.report(err);
|
|
// return {
|
|
// error:
|
|
// 'An error happened. The developers have been notified. Please try again later.',
|
|
// };
|
|
throw new Error(
|
|
'An error happened. The developers have been notified. Please try again later.'
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getCrimes(): Promise<ICrimes[]> {
|
|
const instrumentationService = getInjection('IInstrumentationService');
|
|
return await instrumentationService.instrumentServerAction(
|
|
'District Crime Data',
|
|
{ recordResponse: true },
|
|
async () => {
|
|
try {
|
|
const crimes = await db.crimes.findMany({
|
|
include: {
|
|
districts: {
|
|
select: {
|
|
name: true,
|
|
geographics: {
|
|
select: {
|
|
address: true,
|
|
land_area: true,
|
|
year: true,
|
|
latitude: true,
|
|
longitude: true,
|
|
},
|
|
},
|
|
demographics: {
|
|
select: {
|
|
number_of_unemployed: true,
|
|
population: true,
|
|
population_density: true,
|
|
year: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
crime_incidents: {
|
|
select: {
|
|
id: true,
|
|
timestamp: true,
|
|
description: true,
|
|
status: true,
|
|
crime_categories: {
|
|
select: {
|
|
name: true,
|
|
type: true,
|
|
},
|
|
},
|
|
locations: {
|
|
select: {
|
|
address: true,
|
|
latitude: true,
|
|
longitude: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return crimes;
|
|
} catch (err) {
|
|
if (err instanceof InputParseError) {
|
|
// return {
|
|
// error: err.message,
|
|
// };
|
|
|
|
throw new InputParseError(err.message);
|
|
}
|
|
|
|
if (err instanceof AuthenticationError) {
|
|
// return {
|
|
// error: 'User not found.',
|
|
// };
|
|
|
|
throw new AuthenticationError(
|
|
'There was an error with the credentials. Please try again or contact support.'
|
|
);
|
|
}
|
|
|
|
const crashReporterService = getInjection('ICrashReporterService');
|
|
crashReporterService.report(err);
|
|
// return {
|
|
// error:
|
|
// 'An error happened. The developers have been notified. Please try again later.',
|
|
// };
|
|
throw new Error(
|
|
'An error happened. The developers have been notified. Please try again later.'
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getCrimeByYearAndMonth(
|
|
year: number,
|
|
month: number | 'all'
|
|
): Promise<ICrimesByYearAndMonth[]> {
|
|
const instrumentationService = getInjection('IInstrumentationService');
|
|
return await instrumentationService.instrumentServerAction(
|
|
'District Crime Data',
|
|
{ recordResponse: true },
|
|
async () => {
|
|
try {
|
|
// Build where clause conditionally based on provided parameters
|
|
const whereClause: any = {
|
|
year: year, // Always filter by year now since "all" is removed
|
|
};
|
|
|
|
// Only add month to filter if it's not "all"
|
|
if (month !== 'all') {
|
|
whereClause.month = month;
|
|
}
|
|
|
|
const crimes = await db.crimes.findMany({
|
|
where: whereClause,
|
|
include: {
|
|
districts: {
|
|
select: {
|
|
name: true,
|
|
geographics: {
|
|
where: { year }, // Match geographics to selected year
|
|
select: {
|
|
address: true,
|
|
land_area: true,
|
|
year: true,
|
|
latitude: true,
|
|
longitude: true,
|
|
},
|
|
},
|
|
demographics: {
|
|
where: { year }, // Match demographics to selected year
|
|
select: {
|
|
number_of_unemployed: true,
|
|
population: true,
|
|
population_density: true,
|
|
year: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
crime_incidents: {
|
|
select: {
|
|
id: true,
|
|
timestamp: true,
|
|
description: true,
|
|
status: true,
|
|
crime_categories: {
|
|
select: {
|
|
name: true,
|
|
type: true,
|
|
},
|
|
},
|
|
locations: {
|
|
select: {
|
|
address: true,
|
|
latitude: true,
|
|
longitude: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Process the data to transform geographics and demographics from array to single object
|
|
const processedCrimes = crimes.map((crime) => {
|
|
return {
|
|
...crime,
|
|
districts: {
|
|
...crime.districts,
|
|
// Convert geographics array to single object matching the year
|
|
geographics: crime.districts.geographics[0] || null,
|
|
// Convert demographics array to single object matching the year
|
|
demographics: crime.districts.demographics[0] || null,
|
|
},
|
|
};
|
|
});
|
|
|
|
return processedCrimes;
|
|
} catch (err) {
|
|
if (err instanceof InputParseError) {
|
|
throw new InputParseError(err.message);
|
|
}
|
|
|
|
if (err instanceof AuthenticationError) {
|
|
throw new AuthenticationError(
|
|
'There was an error with the credentials. Please try again or contact support.'
|
|
);
|
|
}
|
|
|
|
const crashReporterService = getInjection('ICrashReporterService');
|
|
crashReporterService.report(err);
|
|
throw new Error(
|
|
'An error happened. The developers have been notified. Please try again later.'
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calculate distances between units and incidents using PostGIS
|
|
* @param unitId Optional unit code to filter by specific unit
|
|
* @param districtId Optional district ID to filter by specific district
|
|
* @returns Array of distance calculations between units and incidents
|
|
*/
|
|
export async function calculateDistances(
|
|
unitId?: string,
|
|
districtId?: string
|
|
): Promise<IDistanceResult[]> {
|
|
const supabase = createClient();
|
|
|
|
try {
|
|
const { data, error } = await supabase.rpc(
|
|
'calculate_unit_incident_distances',
|
|
{
|
|
unit_id: unitId || null,
|
|
district_id: districtId || null,
|
|
}
|
|
);
|
|
|
|
if (error) {
|
|
console.error('Error calculating distances:', error);
|
|
return [];
|
|
}
|
|
|
|
return data || [];
|
|
} catch (error) {
|
|
console.error('Failed to calculate distances:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|