From 7bc30f99d0fb7ae06cc40dadf1e9203e76b0a1af Mon Sep 17 00:00:00 2001 From: Collin Duncan <3679940+cgduncan7@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:33:13 +0100 Subject: [PATCH] Adding ability to monitor court reservations when performing a reservation --- .../migrations/1709910503052-AddMonitors.ts | 15 ++++++ src/app.module.ts | 2 + src/monitoring/entity.ts | 52 +++++++++++++++++++ src/monitoring/module.ts | 14 +++++ src/monitoring/service.ts | 19 +++++++ src/runner/baanreserveren/service.ts | 18 +++++-- test/app.e2e.spec.ts | 26 ++++++++++ 7 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 database/migrations/1709910503052-AddMonitors.ts create mode 100644 src/monitoring/entity.ts create mode 100644 src/monitoring/module.ts create mode 100644 src/monitoring/service.ts create mode 100644 test/app.e2e.spec.ts diff --git a/database/migrations/1709910503052-AddMonitors.ts b/database/migrations/1709910503052-AddMonitors.ts new file mode 100644 index 0000000..d98afe8 --- /dev/null +++ b/database/migrations/1709910503052-AddMonitors.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddMonitors1709910503052 implements MigrationInterface { + name = 'AddMonitors1709910503052' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "monitors" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar(32) NOT NULL, "createdAt" datetime NOT NULL, "data" blob NOT NULL)`, + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "monitors"`) + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 69545ab..e3c93dd 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { LoggerMiddleware } from './logger/middleware' import { LoggerModule } from './logger/module' import { DatabaseLoggerService } from './logger/service.database_logger' import { MembersModule } from './members/module' +import { MonitoringModule } from './monitoring/module' import { NtfyModule } from './ntfy/module' import { RecurringReservationsModule } from './recurringReservations/module' import { ReservationsModule } from './reservations/module' @@ -59,6 +60,7 @@ import { WaitingListModule } from './waitingList/module' MembersModule, WaitingListModule, NtfyModule, + MonitoringModule, ], }) export class AppModule implements NestModule { diff --git a/src/monitoring/entity.ts b/src/monitoring/entity.ts new file mode 100644 index 0000000..4cb7b3c --- /dev/null +++ b/src/monitoring/entity.ts @@ -0,0 +1,52 @@ +import { Transform, Type } from 'class-transformer' +import { TransformationType } from 'class-transformer' +import { Dayjs } from 'dayjs' +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' + +import dayjs from '../common/dayjs' + +export enum MonitorType { + CourtReservations = 'court_reservations', +} + +@Entity({ name: 'monitors' }) +export class Monitor { + @PrimaryGeneratedColumn('uuid') + id: string + + @Column('varchar', { length: 32, nullable: false }) + type: MonitorType + + @Column('datetime', { + nullable: false, + transformer: { + to: (value: Dayjs) => value.format(), + from: (value: Date) => dayjs(value), + }, + }) + @Type(() => Dayjs) + @Transform(({ value, type }) => { + switch (type) { + case TransformationType.PLAIN_TO_CLASS: + return dayjs(value) + case TransformationType.CLASS_TO_PLAIN: + return value.format() + default: + return value + } + }) + createdAt: Dayjs + + @Column('blob', { + nullable: false, + transformer: { + to: (value) => value, + from: (value) => value, + }, + }) + data: unknown + + constructor(partial: Partial) { + Object.assign(this, partial) + } +} diff --git a/src/monitoring/module.ts b/src/monitoring/module.ts new file mode 100644 index 0000000..c25c360 --- /dev/null +++ b/src/monitoring/module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { LoggerModule } from '../logger/module' +import { NtfyModule } from '../ntfy/module' +import { Monitor } from './entity' +import { MonitorsService } from './service' + +@Module({ + imports: [LoggerModule, NtfyModule, TypeOrmModule.forFeature([Monitor])], + providers: [MonitorsService], + exports: [MonitorsService], +}) +export class MonitoringModule {} diff --git a/src/monitoring/service.ts b/src/monitoring/service.ts new file mode 100644 index 0000000..9bff45e --- /dev/null +++ b/src/monitoring/service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' + +import { Monitor, MonitorType } from './entity' + +@Injectable() +export class MonitorsService { + constructor( + @InjectRepository(Monitor) + private readonly monitorsRepository: Repository, + ) {} + + async performMonitor(type: MonitorType, data: unknown) { + await this.monitorsRepository.save( + this.monitorsRepository.create({ type, data }), + ) + } +} diff --git a/src/runner/baanreserveren/service.ts b/src/runner/baanreserveren/service.ts index 23aa83f..47eabdd 100644 --- a/src/runner/baanreserveren/service.ts +++ b/src/runner/baanreserveren/service.ts @@ -509,6 +509,7 @@ export class BaanReserverenService { try { await this.init() await this.navigateToDay(reservation.dateRangeStart) + await this.monitorCourtReservations() await this.selectAvailableTime(reservation) await this.selectOwner(reservation.ownerId) await this.selectOpponent( @@ -591,10 +592,19 @@ export class BaanReserverenService { return courtStatuses } - public async monitorCourtReservations(date: Dayjs) { - await this.init() - await this.navigateToDay(date) - return await this.getAllCourtStatuses() + public async monitorCourtReservations(date?: Dayjs, swallowError = true) { + try { + if (date) { + await this.init() + await this.navigateToDay(date) + } + return await this.getAllCourtStatuses() + } catch (error: unknown) { + this.loggerService.error('Failed to monitor court reservations') + if (!swallowError) { + throw error + } + } } } diff --git a/test/app.e2e.spec.ts b/test/app.e2e.spec.ts new file mode 100644 index 0000000..6e068fe --- /dev/null +++ b/test/app.e2e.spec.ts @@ -0,0 +1,26 @@ +import { INestApplication } from '@nestjs/common' +import { Test, TestingModule } from '@nestjs/testing' +import * as request from 'supertest' + +import { AppModule } from '../src/app.module' + +describe('AppController (e2e)', () => { + let app: INestApplication + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile() + + app = moduleFixture.createNestApplication() + await app.init() + }) + + afterAll(async () => { + await app.close() + }) + + it('/ (GET)', async () => { + await request(app.getHttpServer()).get('/').expect(404) + }) +})