From 62a4b8c9e6e7b90acd5c3b1de1bb7eff3407ccc3 Mon Sep 17 00:00:00 2001 From: Collin Duncan <3679940+cgduncan7@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:23:21 +0200 Subject: [PATCH] Completing ntfy module and adding to other modules --- src/ntfy/module.ts | 3 + src/ntfy/provider.ts | 115 ++++++++++++++++++++++----- src/ntfy/types.ts | 39 +++++---- src/recurringReservations/cron.ts | 11 +++ src/recurringReservations/module.ts | 4 +- src/recurringReservations/service.ts | 1 + src/reservations/cron.ts | 16 ++++ src/reservations/module.ts | 2 + src/reservations/worker.ts | 24 +++++- src/runner/baanreserveren/service.ts | 2 +- src/waitingList/module.ts | 4 +- src/waitingList/service.ts | 4 +- 12 files changed, 176 insertions(+), 49 deletions(-) diff --git a/src/ntfy/module.ts b/src/ntfy/module.ts index 05f4c4e..748afd7 100644 --- a/src/ntfy/module.ts +++ b/src/ntfy/module.ts @@ -1,9 +1,12 @@ +import { BullModule } from '@nestjs/bull' import { Module } from '@nestjs/common' import { NtfyClient } from './client' import { NtfyProvider } from './provider' +import { NTFY_PUBLISH_QUEUE_NAME } from './types' @Module({ + imports: [BullModule.registerQueue({ name: NTFY_PUBLISH_QUEUE_NAME })], providers: [NtfyProvider, NtfyClient], exports: [NtfyProvider, NtfyClient], }) diff --git a/src/ntfy/provider.ts b/src/ntfy/provider.ts index 98db5f9..2023806 100644 --- a/src/ntfy/provider.ts +++ b/src/ntfy/provider.ts @@ -1,38 +1,113 @@ +import { InjectQueue, Process, Processor } from '@nestjs/bull' import { Inject, Injectable } from '@nestjs/common' +import { Job, JobOptions, Queue } from 'bull' +import { Dayjs } from 'dayjs' import { NtfyClient } from './client' -import { MessagePriority, MessageTags } from './types' +import { + MessageConfig, + MessagePriority, + MessageTags, + NTFY_PUBLISH_QUEUE_NAME, +} from './types' +@Processor(NTFY_PUBLISH_QUEUE_NAME) @Injectable() export class NtfyProvider { constructor( @Inject(NtfyClient) private readonly ntfyClient: NtfyClient, + + @InjectQueue(NTFY_PUBLISH_QUEUE_NAME) + private readonly publishQueue: Queue, ) {} - async sendErrorNotification( - title: string, - message: string, - priority = MessagePriority.default, - ) { + @Process() + async handlePublishJob(job: Job>) { await this.ntfyClient.publish({ - title, - message, - tags: [MessageTags.red_x], - priority, + ...job.data, }) } - async sendInfoNotification( - title: string, - message: string, - priority = MessagePriority.low, + private static defaultJob( + data: Omit, + ): [Omit, JobOptions] { + return [ + data, + { + attempts: 3, + removeOnComplete: true, + backoff: { + type: 'exponential', + }, + }, + ] + } + + async sendCronStartNotification(title: string) { + await this.publishQueue.add( + ...NtfyProvider.defaultJob({ + title, + tags: [MessageTags.alarm_clock, MessageTags.green_circle], + }), + ) + } + + async sendCronStopNotification(title: string, message: string) { + await this.publishQueue.add( + ...NtfyProvider.defaultJob({ + title, + message, + tags: [MessageTags.alarm_clock, MessageTags.red_circle], + }), + ) + } + + async sendPerformingReservationNotification( + reservationId: string, + startTime: Dayjs, + endTime: Dayjs, ) { - await this.ntfyClient.publish({ - title, - message, - tags: [MessageTags.info], - priority, - }) + await this.publishQueue.add( + ...NtfyProvider.defaultJob({ + title: 'Performing reservation', + message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`, + tags: [MessageTags.badminton], + priority: MessagePriority.low, + }), + ) + } + + async sendErrorPerformingReservationNotification( + reservationId: string, + startTime: Dayjs, + endTime: Dayjs, + error: Error, + ) { + await this.publishQueue.add( + ...NtfyProvider.defaultJob({ + title: 'Error performing reservation', + message: `${reservationId} - ${startTime.format()} to ${endTime.format()} : (${ + error.name + }) - ${error.message}`, + tags: [MessageTags.badminton, MessageTags.red_x], + priority: MessagePriority.low, + }), + ) + } + + async sendReservationWaitlistedNotification( + reservationId: string, + startTime: Dayjs, + endTime: Dayjs, + ) { + await this.publishQueue.add( + ...NtfyProvider.defaultJob({ + title: 'Reservation waitlisted', + message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`, + tags: [MessageTags.badminton, MessageTags.hourglass], + priority: MessagePriority.low, + }), + ) } } diff --git a/src/ntfy/types.ts b/src/ntfy/types.ts index 1d1795b..813fff6 100644 --- a/src/ntfy/types.ts +++ b/src/ntfy/types.ts @@ -1,31 +1,29 @@ +export const NTFY_PUBLISH_QUEUE_NAME = 'ntfy_publish_queue' + export enum MessageTags { - thumbs_up = '+1', - thumbs_down = '-1', + alarm_clock = 'alarm_clock', + green_circle = 'green_circle', + yellow_circle = 'yellow_circle', + red_circle = 'red_circle', + info = 'information_source', + hourglass = 'hourglass', warning = 'warning', - rotating_light = 'rotating-light', - skull_and_crossbones = 'skull_and_crossbones', - tada = 'tada', - outbox_tray = 'outbox_tray', - inbox_tray = 'inbox_tray', - date = 'date', - no_entry = 'no_entry', exclamation = 'exclamation', question = 'question', + no_entry = 'no_entry', white_check_mark = 'white_check_mark', red_x = 'x', zero = 'zero', - one = 'zero', - two = 'zero', - three = 'zero', - four = 'zero', - five = 'zero', - six = 'zero', - seven = 'zero', - eight = 'zero', - nine = 'zero', + one = 'one', + two = 'two', + three = 'three', + four = 'four', + five = 'five', + six = 'six', + seven = 'seven', + eight = 'eight', + nine = 'nine', new = 'new', - ok = 'ok', - sos = 'sos', clock12 = 'clock12', clock1230 = 'clock1230', clock1 = 'clock1', @@ -51,7 +49,6 @@ export enum MessageTags { clock11 = 'clock11', clock1130 = 'clock1130', badminton = 'badminton', - info = 'information_source', } export enum MessagePriority { diff --git a/src/recurringReservations/cron.ts b/src/recurringReservations/cron.ts index a4c7ff4..eba0444 100644 --- a/src/recurringReservations/cron.ts +++ b/src/recurringReservations/cron.ts @@ -3,6 +3,7 @@ import { Cron, CronExpression } from '@nestjs/schedule' import dayjs from '../common/dayjs' import { LoggerService } from '../logger/service.logger' +import { NtfyProvider } from '../ntfy/provider' import { RecurringReservationsService } from './service' @Injectable() @@ -13,6 +14,9 @@ export class RecurringReservationsCronService { @Inject(LoggerService) private readonly loggerService: LoggerService, + + @Inject(NtfyProvider) + private readonly ntfyProvider: NtfyProvider, ) {} @Cron(CronExpression.EVERY_DAY_AT_4AM, { @@ -21,6 +25,9 @@ export class RecurringReservationsCronService { }) async handleRecurringReservations() { this.loggerService.log('handleRecurringReservations beginning') + await this.ntfyProvider.sendCronStartNotification( + 'handleRecurringReservations', + ) const dayOfWeek = dayjs().get('day') const recurringReservationsToSchedule = await this.recurringReservationsService.getByDayOfWeek(dayOfWeek) @@ -33,5 +40,9 @@ export class RecurringReservationsCronService { ) } this.loggerService.log('handleRecurringReservations ending') + await this.ntfyProvider.sendCronStopNotification( + 'handleRecurringReservations', + `Count: ${recurringReservationsToSchedule.length}`, + ) } } diff --git a/src/recurringReservations/module.ts b/src/recurringReservations/module.ts index 986fdfe..4268e7d 100644 --- a/src/recurringReservations/module.ts +++ b/src/recurringReservations/module.ts @@ -1,8 +1,9 @@ import { Module } from '@nestjs/common' import { TypeOrmModule } from '@nestjs/typeorm' -import { ReservationsModule } from 'src/reservations/module' import { LoggerModule } from '../logger/module' +import { NtfyModule } from '../ntfy/module' +import { ReservationsModule } from '../reservations/module' import { RunnerModule } from '../runner/module' import { RecurringReservationsController } from './controller' import { RecurringReservationsCronService } from './cron' @@ -15,6 +16,7 @@ import { RecurringReservationsService } from './service' TypeOrmModule.forFeature([RecurringReservation]), RunnerModule, ReservationsModule, + NtfyModule, ], controllers: [RecurringReservationsController], providers: [RecurringReservationsService, RecurringReservationsCronService], diff --git a/src/recurringReservations/service.ts b/src/recurringReservations/service.ts index ae3ac13..f9b2651 100644 --- a/src/recurringReservations/service.ts +++ b/src/recurringReservations/service.ts @@ -10,6 +10,7 @@ export class RecurringReservationsService { constructor( @InjectRepository(RecurringReservation) private recurringReservationsRepository: Repository, + @Inject(ReservationsService) private reservationsService: ReservationsService, ) {} diff --git a/src/reservations/cron.ts b/src/reservations/cron.ts index b76ef3a..c4d333b 100644 --- a/src/reservations/cron.ts +++ b/src/reservations/cron.ts @@ -4,6 +4,7 @@ import { Cron, CronExpression } from '@nestjs/schedule' import { Queue } from 'bull' import { LoggerService } from '../logger/service.logger' +import { NtfyProvider } from '../ntfy/provider' import { RESERVATIONS_QUEUE_NAME } from './config' import { ReservationsService } from './service' @@ -16,6 +17,9 @@ export class ReservationsCronService { @InjectQueue(RESERVATIONS_QUEUE_NAME) private readonly reservationsQueue: Queue, + @Inject(NtfyProvider) + private readonly ntfyProvider: NtfyProvider, + @Inject(LoggerService) private readonly loggerService: LoggerService, ) {} @@ -26,6 +30,7 @@ export class ReservationsCronService { }) async handleDailyReservations() { this.loggerService.log('handleDailyReservations beginning') + await this.ntfyProvider.sendCronStartNotification('handleDailyReservations') const reservationsToPerform = await this.reservationService.getSchedulable() this.loggerService.log( `Found ${reservationsToPerform.length} reservations to perform`, @@ -34,6 +39,10 @@ export class ReservationsCronService { reservationsToPerform.map((r) => ({ data: r, opts: { attempts: 1 } })), ) this.loggerService.log('handleDailyReservations ending') + await this.ntfyProvider.sendCronStopNotification( + 'handleDailyReservations', + `Count: ${reservationsToPerform.length}`, + ) } @Cron(CronExpression.EVERY_DAY_AT_11PM, { @@ -42,6 +51,9 @@ export class ReservationsCronService { }) async cleanUpExpiredReservations() { this.loggerService.log('cleanUpExpiredReservations beginning') + await this.ntfyProvider.sendCronStartNotification( + 'cleanUpExpiredReservations', + ) const reservations = await this.reservationService.getByDate() this.loggerService.log( `Found ${reservations.length} reservations to delete`, @@ -50,5 +62,9 @@ export class ReservationsCronService { await this.reservationService.deleteById(reservation.id) } this.loggerService.log('cleanUpExpiredReservations ending') + await this.ntfyProvider.sendCronStopNotification( + 'cleanUpExpiredReservations', + `Count: ${reservations.length}`, + ) } } diff --git a/src/reservations/module.ts b/src/reservations/module.ts index 63192b1..0d2de11 100644 --- a/src/reservations/module.ts +++ b/src/reservations/module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common' import { TypeOrmModule } from '@nestjs/typeorm' import { LoggerModule } from '../logger/module' +import { NtfyModule } from '../ntfy/module' import { RunnerModule } from '../runner/module' import { RESERVATIONS_QUEUE_NAME } from './config' import { ReservationsController } from './controller' @@ -17,6 +18,7 @@ import { ReservationsWorker } from './worker' TypeOrmModule.forFeature([Reservation]), BullModule.registerQueue({ name: RESERVATIONS_QUEUE_NAME }), RunnerModule, + NtfyModule, ], exports: [ReservationsService], controllers: [ReservationsController], diff --git a/src/reservations/worker.ts b/src/reservations/worker.ts index f766b6e..54bc148 100644 --- a/src/reservations/worker.ts +++ b/src/reservations/worker.ts @@ -4,6 +4,7 @@ import { Job } from 'bull' import { instanceToPlain, plainToInstance } from 'class-transformer' import { LoggerService } from '../logger/service.logger' +import { NtfyProvider } from '../ntfy/provider' import { BaanReserverenService, NoCourtAvailableError, @@ -23,6 +24,9 @@ export class ReservationsWorker { @Inject(LoggerService) private readonly loggerService: LoggerService, + + @Inject(NtfyProvider) + private readonly ntfyProvider: NtfyProvider, ) {} @Process() @@ -33,11 +37,16 @@ export class ReservationsWorker { this.loggerService.log('Handling reservation', { reservation: instanceToPlain(reservation), }) + await this.ntfyProvider.sendPerformingReservationNotification( + reservation.id, + reservation.dateRangeStart, + reservation.dateRangeEnd, + ) await this.performReservation(reservation) } private async handleReservationErrors( - error: unknown, + error: Error, reservation: Reservation, ) { switch (true) { @@ -45,12 +54,23 @@ export class ReservationsWorker { this.loggerService.warn('No court available') if (!reservation.waitListed) { this.loggerService.log('Adding reservation to waiting list') + await this.ntfyProvider.sendReservationWaitlistedNotification( + reservation.id, + reservation.dateRangeStart, + reservation.dateRangeEnd, + ) await this.addReservationToWaitList(reservation) } return } default: this.loggerService.error('Error while performing reservation', error) + this.ntfyProvider.sendErrorPerformingReservationNotification( + reservation.id, + reservation.dateRangeStart, + reservation.dateRangeEnd, + error, + ) throw error } } @@ -60,7 +80,7 @@ export class ReservationsWorker { await this.brService.performReservation(reservation) await this.reservationsService.deleteById(reservation.id) } catch (error: unknown) { - await this.handleReservationErrors(error, reservation) + await this.handleReservationErrors(error as Error, reservation) } } diff --git a/src/runner/baanreserveren/service.ts b/src/runner/baanreserveren/service.ts index 8089971..0314be1 100644 --- a/src/runner/baanreserveren/service.ts +++ b/src/runner/baanreserveren/service.ts @@ -2,9 +2,9 @@ import { Inject, Injectable } from '@nestjs/common' import { instanceToPlain } from 'class-transformer' import { Dayjs } from 'dayjs' import { ElementHandle, Page } from 'puppeteer' -import { LoggerService } from 'src/logger/service.logger' import dayjs from '../../common/dayjs' +import { LoggerService } from '../../logger/service.logger' import { Reservation } from '../../reservations/entity' import { EmptyPage } from '../pages/empty' diff --git a/src/waitingList/module.ts b/src/waitingList/module.ts index d601996..8f61aa3 100644 --- a/src/waitingList/module.ts +++ b/src/waitingList/module.ts @@ -1,11 +1,11 @@ import { BullModule } from '@nestjs/bull' import { Module } from '@nestjs/common' -import { EmailModule } from 'src/email/module' -import { ReservationsModule } from 'src/reservations/module' import { EMAILS_QUEUE_NAME } from '../email/config' +import { EmailModule } from '../email/module' import { LoggerModule } from '../logger/module' import { RESERVATIONS_QUEUE_NAME } from '../reservations/config' +import { ReservationsModule } from '../reservations/module' import { WaitingListService } from './service' @Module({ diff --git a/src/waitingList/service.ts b/src/waitingList/service.ts index cddba33..878c3e5 100644 --- a/src/waitingList/service.ts +++ b/src/waitingList/service.ts @@ -1,14 +1,14 @@ import { InjectQueue, Process, Processor } from '@nestjs/bull' import { Inject } from '@nestjs/common' import { Job, Queue } from 'bull' -import { EmailProvider } from 'src/email/provider' -import { ReservationsService } from 'src/reservations/service' import dayjs from '../common/dayjs' import { EMAILS_QUEUE_NAME } from '../email/config' +import { EmailProvider } from '../email/provider' import { Email } from '../email/types' import { LoggerService } from '../logger/service.logger' import { RESERVATIONS_QUEUE_NAME } from '../reservations/config' +import { ReservationsService } from '../reservations/service' import { WaitingListDetails } from './types' const EMAIL_SUBJECT_REGEX = new RegExp(