diff --git a/apps/server/src/Services/Notifier/Notifier/DeviceNotifier.ts b/apps/server/src/Services/Notifier/Notifier/DeviceNotifier.ts index 6fb230ce9..9cfc6089e 100644 --- a/apps/server/src/Services/Notifier/Notifier/DeviceNotifier.ts +++ b/apps/server/src/Services/Notifier/Notifier/DeviceNotifier.ts @@ -2,13 +2,35 @@ import {type NotificationParameters} from '../../../Interfaces/NotificationParam import type Notifier from '../Notifier'; import {NOTIFICATION_METHOD} from '../methodConstants'; import {env} from '../../../env.mjs'; -import {logger} from '../../../../src/server/logger'; +import {logger} from '../../../server/logger'; +import {prisma} from '../../../server/db'; class DeviceNotifier implements Notifier { getSupportedMethods(): Array { return [NOTIFICATION_METHOD.DEVICE]; } + async deleteNotificationAndDevice(destination: string, notificationId: string): Promise { + try { + // Delete the notification + await prisma.notification.delete({ + where: { + id: notificationId, + }, + }); + // Unverify and disable the alertMethod + await prisma.alertMethod.deleteMany({ + where: { + destination: destination, + method: NOTIFICATION_METHOD.DEVICE, + } + }); + logger(`Notification with ID: ${notificationId} deleted and alertMethod for destination: ${destination} has been unverified and disabled.`, "info"); + } catch (error) { + logger(`Database Error: Couldn't modify the alertMethod or delete the notification: ${error}`, "error"); + } + } + // OneSignal can send both iOS and android notifications, // "destination" from AlertMethod for method "device" // is the OneSignal player ID of the device. @@ -46,6 +68,10 @@ class DeviceNotifier implements Notifier { `Failed to send device notification. Error: ${response.statusText} for ${parameters.id}`, 'error', ); + // If device not found + if(response.status === 404){ + await this.deleteNotificationAndDevice(destination, parameters.id) + } return false; } diff --git a/apps/server/src/Services/Notifier/Notifier/WebhookNotifier.ts b/apps/server/src/Services/Notifier/Notifier/WebhookNotifier.ts index be2ed002f..ea5046b29 100644 --- a/apps/server/src/Services/Notifier/Notifier/WebhookNotifier.ts +++ b/apps/server/src/Services/Notifier/Notifier/WebhookNotifier.ts @@ -1,3 +1,4 @@ +import {prisma} from '../../../server/db'; import {logger} from '../../../../src/server/logger'; import {type NotificationParameters} from '../../../Interfaces/NotificationParameters'; import type Notifier from '../Notifier'; @@ -8,6 +9,31 @@ class WebhookNotifier implements Notifier { return [NOTIFICATION_METHOD.WEBHOOK]; } + async deleteNotificationDisableAndUnverifyWebhook(destination: string, notificationId: string): Promise { + try { + // Delete the notification + await prisma.notification.delete({ + where: { + id: notificationId, + }, + }); + // Unverify and disable the alertMethod + await prisma.alertMethod.updateMany({ + where: { + destination: destination, + method: NOTIFICATION_METHOD.WEBHOOK, + }, + data: { + isVerified: false, + isEnabled: false, + }, + }); + logger(`Notification with ID: ${notificationId} deleted and alertMethod for destination: ${destination} has been unverified and disabled.`, "info"); + } catch (error) { + logger(`Database Error: Couldn't modify the alertMethod or delete the notification: ${error}`, "error"); + } + } + async notify( destination: string, parameters: NotificationParameters, @@ -34,12 +60,27 @@ class WebhookNotifier implements Notifier { if (!response.ok) { logger( - `Failed to send webhook notification. Error: ${response.statusText} for ${parameters.id}`, + `Failed to send webhook notification. Error: ${response.statusText} for ${parameters.id}.`, 'error', ); + // Specific status code handling + if (response.status === 404) { + // Webhook URL Not Found - Token not found + await this.deleteNotificationDisableAndUnverifyWebhook(destination, parameters.id); + } else if (response.status === 401){ + // Unauthorized + await this.deleteNotificationDisableAndUnverifyWebhook(destination, parameters.id); + } else if (response.status === 403){ + // Forbidden + await this.deleteNotificationDisableAndUnverifyWebhook(destination, parameters.id); + } else { + logger( + `Failed to send webhook notification. Something went wrong. Try again in next run.`, + 'error', + ); + } return false; } - return true; } } diff --git a/apps/server/src/server/api/zodSchemas/alertMethod.schema.ts b/apps/server/src/server/api/zodSchemas/alertMethod.schema.ts index 3c6f61a90..9f1d4b694 100644 --- a/apps/server/src/server/api/zodSchemas/alertMethod.schema.ts +++ b/apps/server/src/server/api/zodSchemas/alertMethod.schema.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import {z} from 'zod'; import phone from 'phone' import validator from 'validator'; @@ -6,26 +6,29 @@ export const createAlertMethodSchema = z.object({ method: z.enum(["email", "sms", "device", "whatsapp", "webhook"]), destination: z.string({ required_error: 'Destination of alert method must be specified' - }).refine((value) => { - const sanitized = validator.escape(value); - return sanitized === value; - }, { - message: 'Contains invalid characters', - }), + }), deviceName: z.string().optional(), deviceId: z.string().optional(), }).refine((obj) => { - if (obj.method === 'sms') { - // Check if the destination is a valid phone number in E.164 format - const { isValid } = phone(obj.destination); - return isValid; - } - if (obj.method === 'email') { - return z.string().email().safeParse(obj.destination).success; + if(obj.method === 'webhook'){ + return true; + }else{ + const sanitized = validator.escape(obj.destination); + if (sanitized !== obj.destination){ + return false; + } + if (obj.method === 'sms') { + // Check if the destination is a valid phone number in E.164 format + const { isValid } = phone(obj.destination); + return isValid; + } + if (obj.method === 'email') { + return z.string().email().safeParse(obj.destination).success; + } + return true; // Return true for other methods } - return true; // Return true for other methods }, { - message: 'Must be a valid phone number in E.164 format when the method is "sms"' + message: `Invalid Destination` }); export const params = z.object({