'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 { 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 { 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( p_unit_id?: string, p_district_id?: string ): Promise { const instrumentationService = getInjection('IInstrumentationService'); return await instrumentationService.instrumentServerAction( 'Calculate Distances', { recordResponse: true }, async () => { const supabase = createClient(); try { const { data, error } = await supabase.rpc( 'calculate_unit_incident_distances', { p_unit_id: p_unit_id || null, p_district_id: p_district_id || null, } ); if (error) { console.error('Error calculating distances:', error); return []; } return data as IDistanceResult[] || []; } catch (error) { const crashReporterService = getInjection('ICrashReporterService'); crashReporterService.report(error); console.error('Failed to calculate distances:', error); return []; } } ); }