From 7af46bbdcd1404a6789a307aa59535f7b65dd850 Mon Sep 17 00:00:00 2001 From: Aashish Dhakal <85501584+dhakalaashish@users.noreply.github.com> Date: Thu, 17 Aug 2023 20:04:39 +0545 Subject: [PATCH] create siteAlert procedure --- apps/server/src/server/api/routers/alert.ts | 143 +++++++++++++++++- .../server/src/server/api/routers/geoEvent.ts | 11 +- .../src/server/api/zodSchemas/alert.schema.ts | 12 +- 3 files changed, 156 insertions(+), 10 deletions(-) diff --git a/apps/server/src/server/api/routers/alert.ts b/apps/server/src/server/api/routers/alert.ts index 288f0c849..83470a91a 100644 --- a/apps/server/src/server/api/routers/alert.ts +++ b/apps/server/src/server/api/routers/alert.ts @@ -1,11 +1,13 @@ import { TRPCError } from "@trpc/server"; -import { queryAlertSchema } from '../zodSchemas/alert.schema' +import { queryAlertSchema, createAlertSchema } from '../zodSchemas/alert.schema' import { createTRPCRouter, protectedProcedure, publicProcedure, } from "../trpc"; import { getLocalTime, subtractDays } from "../../../utils/date"; +import { GeoEventProvider } from "@prisma/client"; +import { createXXHash3 } from "hash-wasm"; export const alertRouter = createTRPCRouter({ @@ -141,4 +143,143 @@ export const alertRouter = createTRPCRouter({ }); } }), + + create: protectedProcedure + .input(createAlertSchema) + .mutation(async ({ ctx, input }) => { + try { + const { + siteId, + type, + latitude, + longitude, + eventDate: inputEventDate, + detectedBy: geoEventProviderClientId, + confidence, + ...rest + } = input; + const geoEventProviderClientApiKey = ctx.req.headers["x-api-key"]; + + // Ensure the user is authenticated + //Authentication ensure user is authenticated either with access token or with GeoEventProviderApiKey + if (!geoEventProviderClientApiKey && !ctx.user) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: `Missing Authorization header`, + }); + } + + if (!geoEventProviderClientId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: `Missing Provider Client Id Authorization`, + }); + } + + // Check whether the User is a GeoEventProviderClient or if the request has a GeoEventProviderApiKey and GeoEventProviderClientId + // Logic: + // get geoeventprovider from database where clientId = geoEventProviderClientId and (apiKey = geoEventProviderApiKey or userId = user.id) + // if no geoeventprovider is found throw error + // This logic ensures that either a geoEventProviderClient can continue, or that the one accessing this route must have a correct geoEventProviderClientKey + + let provider: (GeoEventProvider | null) = null; + + // If apiKey exists and is a string + if (geoEventProviderClientApiKey && typeof geoEventProviderClientApiKey === 'string') { + // Find provider where clientId and apiKey + provider = await ctx.prisma.geoEventProvider.findFirst({ + where: { + clientId: geoEventProviderClientId, + clientApiKey: geoEventProviderClientApiKey, + }, + }); + } else if (ctx.user?.id) { + // Find provider where clientId and userId + provider = await ctx.prisma.geoEventProvider.findFirst({ + where: { + clientId: geoEventProviderClientId, + userId: ctx.user?.id, + }, + }); + } + + if (!provider) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `Provider not found`, + }); + } + + // Get site from the database using siteId; if not found, throw an error + const site = await ctx.prisma.site.findUnique({ where: { id: siteId } }); + if (!site) { + throw new TRPCError({ + code: "NOT_FOUND", + message: `Site Not Found`, + }); + } + + // To ensure same data isn't stored twice we will use id as a unique identifier + // generated from a hash of latitude, longitude, eventDate, type and x-client-id + // This will allow us to store the same event multiple times if it is from different providers + // but will not store the same event twice from the same provider + + // Create checksum + const hasher = await createXXHash3(); + hasher.init(); // Reset the hasher + const eventDate = inputEventDate ? inputEventDate : new Date() + const checksum = hasher.update( + latitude.toString() + + longitude.toString() + + eventDate.toISOString() + + type + + geoEventProviderClientId + ).digest('hex'); + + // Verify if the event already exists + const existingSiteAlert = await ctx.prisma.siteAlert.findUnique({ where: { id: checksum } }); + + // If the event already exists, send a success message saying the creation process was cancelled + // Because the event was already stored in our database. + if (existingSiteAlert) { + return { + status: 'success', + message: 'Cancelled. This alert was already present in the database.' + } + } + + // Create SiteAlert + const siteAlert = await ctx.prisma.siteAlert.create({ + data: { + siteId, + type, + latitude, + longitude, + eventDate: eventDate, + detectedBy: geoEventProviderClientId, + confidence, + ...rest, + isProcessed: false, + }, + }); + + // Return success message with the created siteAlert + return { + status: 'success', + data: siteAlert, + }; + } + catch (error) { + console.log(error); + if (error instanceof TRPCError) { + // If the error is already a TRPCError, just re-throw it + throw error; + } + // If it's a different type of error, throw a new TRPCError + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `${error}`, + }); + } + }), }); diff --git a/apps/server/src/server/api/routers/geoEvent.ts b/apps/server/src/server/api/routers/geoEvent.ts index 42298d226..006a367c8 100644 --- a/apps/server/src/server/api/routers/geoEvent.ts +++ b/apps/server/src/server/api/routers/geoEvent.ts @@ -37,15 +37,13 @@ export const geoEventRouter = createTRPCRouter({ throw new TRPCError({ code: "BAD_REQUEST", message: `The value of req.headers['x-client-id'] must be a string` }); } + // Check whether the User is a GeoEventProviderClient or if the request has a GeoEventProviderApiKey and GeoEventProviderClientId + // Logic: // get geoeventprovider from database where clientId = geoEventProviderClientId and (apiKey = geoEventProviderApiKey or userId = user.id) // if no geoeventprovider is found throw error - // if geoeventprovider is found and apiKey is not equal to geoEventProviderClientApiKey - // throw error - // if geoeventprovider is found and userId is not equal to user.id continue normally - // if geoeventprovider is found and userId is equal to user.id continue normally + // This logic ensures that either a geoEventProviderClient can continue, or that the one accessing this route must have a correct geoEventProviderClientKey - // Aashish: The code below replicates above commented logic. let provider: (GeoEventProvider | null) = null; // If apiKey exists and is a string @@ -81,9 +79,6 @@ export const geoEventRouter = createTRPCRouter({ }); } - - - // To ensure same data isn't stored twice we will use id as a unique identifier // generated from a hash of latitude, longitude, eventDate, type and x-client-id // This will allow us to store the same event multiple times if it is from different providers diff --git a/apps/server/src/server/api/zodSchemas/alert.schema.ts b/apps/server/src/server/api/zodSchemas/alert.schema.ts index fe3f79281..4d17afd09 100644 --- a/apps/server/src/server/api/zodSchemas/alert.schema.ts +++ b/apps/server/src/server/api/zodSchemas/alert.schema.ts @@ -4,7 +4,17 @@ export const queryAlertSchema = z.object({ id: z.string(), }) - +export const createAlertSchema = z.object({ + siteId: z.string(), + type: z.enum(["fire"]), + latitude: z.number(), + longitude: z.number(), + eventDate: z.date().optional(), + detectedBy: z.string(), + confidence: z.enum(["medium", "low", "high"]), + distance: z.number().optional(), + data: z.record(z.unknown()).optional(), +});