Completing ntfy module and adding to other modules

This commit is contained in:
Collin Duncan 2023-09-06 11:23:21 +02:00
parent 2f7d5c68ef
commit 62a4b8c9e6
No known key found for this signature in database
12 changed files with 176 additions and 49 deletions

View file

@ -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],
})

View file

@ -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<Omit<MessageConfig, 'topic'>>) {
await this.ntfyClient.publish({
title,
message,
tags: [MessageTags.red_x],
priority,
...job.data,
})
}
async sendInfoNotification(
title: string,
message: string,
priority = MessagePriority.low,
) {
await this.ntfyClient.publish({
private static defaultJob(
data: Omit<MessageConfig, 'topic'>,
): [Omit<MessageConfig, 'topic'>, 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.info],
priority,
})
tags: [MessageTags.alarm_clock, MessageTags.red_circle],
}),
)
}
async sendPerformingReservationNotification(
reservationId: string,
startTime: Dayjs,
endTime: Dayjs,
) {
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,
}),
)
}
}

View file

@ -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 {

View file

@ -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}`,
)
}
}

View file

@ -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],

View file

@ -10,6 +10,7 @@ export class RecurringReservationsService {
constructor(
@InjectRepository(RecurringReservation)
private recurringReservationsRepository: Repository<RecurringReservation>,
@Inject(ReservationsService)
private reservationsService: ReservationsService,
) {}

View file

@ -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}`,
)
}
}

View file

@ -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],

View file

@ -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)
}
}

View file

@ -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'

View file

@ -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({

View file

@ -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(