Compare commits
3 commits
main
...
safe_waiti
| Author | SHA1 | Date | |
|---|---|---|---|
| f99550d879 | |||
|
|
bedd062aa0 | ||
| 736e1f4d39 |
23 changed files with 920 additions and 589 deletions
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
v24.13.0
|
||||
lts/hydrogen
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ when:
|
|||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: docker.io/node:24
|
||||
environment:
|
||||
PUPPETEER_SKIP_DOWNLOAD: true
|
||||
image: docker.io/node:hydrogen
|
||||
volumes:
|
||||
- /etc/ssh:/etc/ssh
|
||||
commands:
|
||||
|
|
@ -17,3 +15,5 @@ steps:
|
|||
|
||||
depends_on:
|
||||
- test
|
||||
|
||||
runs_on: [success]
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ when:
|
|||
|
||||
steps:
|
||||
- name: test
|
||||
image: docker.io/node:24-slim
|
||||
environment:
|
||||
PUPPETEER_SKIP_DOWNLOAD: true
|
||||
image: docker.io/node:hydrogen
|
||||
commands:
|
||||
- npm ci
|
||||
- npm run test:unit
|
||||
|
|
|
|||
22
docker/docker-compose.yml
Normal file
22
docker/docker-compose.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
services:
|
||||
http:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: ./docker/server/Dockerfile
|
||||
args:
|
||||
- PORT=3000
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
restart: always
|
||||
environment:
|
||||
- PORT=3000
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ../db:/app/db
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379
|
||||
32
docker/server/Dockerfile
Normal file
32
docker/server/Dockerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
FROM node:18 as app
|
||||
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
||||
ENV PUPPETEER_EXECUTABLE_PATH /usr/bin/chromium
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y gcc chromium fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends
|
||||
RUN npm i -g node-gyp
|
||||
|
||||
WORKDIR /app
|
||||
RUN chown -R node:node .
|
||||
|
||||
USER node
|
||||
|
||||
COPY --chown=node:node package.json package.json
|
||||
COPY --chown=node:node package-lock.json package-lock.json
|
||||
|
||||
RUN CXX=g++-12 npm install
|
||||
|
||||
COPY --chown=node:node data-source.ts data-source.ts
|
||||
COPY --chown=node:node tsconfig.json tsconfig.json
|
||||
COPY --chown=node:node tsconfig.build.json tsconfig.build.json
|
||||
COPY --chown=node:node database database
|
||||
COPY --chown=node:node src src
|
||||
|
||||
RUN npm run migrations
|
||||
|
||||
ARG GIT_COMMIT
|
||||
|
||||
ENV GIT_COMMIT ${GIT_COMMIT:-unknown}
|
||||
|
||||
ENTRYPOINT ["npm", "run", "start"]
|
||||
|
|
@ -3,7 +3,7 @@ module.exports = {
|
|||
{
|
||||
name: 'autobaan',
|
||||
script: 'npm',
|
||||
args: ['run', 'start:prod'],
|
||||
args: 'run start',
|
||||
},
|
||||
],
|
||||
deploy: {
|
||||
|
|
@ -13,9 +13,8 @@ module.exports = {
|
|||
ref: 'origin/main',
|
||||
repo: 'https://fred.collinduncan.com/collin/autobaan.git',
|
||||
path: '/root/autobaan',
|
||||
'pre-deploy': 'npm install && npm run build',
|
||||
'post-deploy':
|
||||
'GIT_COMMIT=$(git show -s --format=%h) pm2 startOrRestart ecosystem.config.js --name autobaan --update-env',
|
||||
'npm install && GIT_COMMIT=$(git show -s --format=%h) pm2 startOrRestart ecosystem.config.js --name autobaan --update-env',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
982
package-lock.json
generated
982
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@
|
|||
"prestart:dev": "npm run migrations",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/src/main",
|
||||
"start:prod": "node dist/main",
|
||||
"repl": "npm run start -- --entryFile repl",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
|
|
@ -38,7 +38,6 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"imap": "^0.8.19",
|
||||
"mailparser-mit": "^1.0.0",
|
||||
"prom-client": "^15.1.3",
|
||||
"puppeteer": "^20.4.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.2.0",
|
||||
|
|
@ -63,7 +62,7 @@
|
|||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"jest": "29.5.0",
|
||||
"pm2": "^7.0.1",
|
||||
"pm2": "^6.0.5",
|
||||
"prettier": "^2.3.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"supertest": "^6.1.3",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import { DatabaseLoggerService } from './logger/service.database_logger'
|
|||
import { MembersModule } from './members/module'
|
||||
import { MonitoringModule } from './monitoring/module'
|
||||
import { NtfyModule } from './ntfy/module'
|
||||
import { PrometheusModule } from './prometheus/module'
|
||||
import { RecurringReservationsModule } from './recurringReservations/module'
|
||||
import { ReservationsModule } from './reservations/module'
|
||||
import { RunnerModule } from './runner/module'
|
||||
|
|
@ -64,7 +63,6 @@ import { WaitingListModule } from './waitingList/module'
|
|||
WaitingListModule,
|
||||
NtfyModule,
|
||||
MonitoringModule,
|
||||
PrometheusModule,
|
||||
],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config'
|
|||
import { NestFactory } from '@nestjs/core'
|
||||
|
||||
import { AppModule } from './app.module'
|
||||
import { CustomResponseTransformInterceptor } from './common/customResponse'
|
||||
import { setDefaults } from './common/dayjs'
|
||||
|
||||
async function bootstrap() {
|
||||
|
|
@ -14,6 +15,7 @@ async function bootstrap() {
|
|||
const port = config.get('PORT', 3000)
|
||||
app.enableShutdownHooks()
|
||||
app.useGlobalPipes(new ValidationPipe({ transform: true }))
|
||||
app.useGlobalInterceptors(new CustomResponseTransformInterceptor())
|
||||
setDefaults()
|
||||
await app.listen(port, () => console.log(`Listening on port ${port}`))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { Controller, Get, Inject, Query, UseInterceptors } from '@nestjs/common'
|
||||
import { Controller, Get, Inject, Query } from '@nestjs/common'
|
||||
|
||||
import { CustomResponseTransformInterceptor } from '../common/customResponse'
|
||||
import { MembersService } from './service'
|
||||
|
||||
@Controller('members')
|
||||
@UseInterceptors(CustomResponseTransformInterceptor)
|
||||
export class MembersController {
|
||||
constructor(
|
||||
@Inject(MembersService)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class NtfyClient {
|
|||
throw new Error(`${response.status} - ${response.statusText}`)
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
this.loggerService.error('ntfy client failed', { error: error instanceof Error ? error.message : JSON.stringify(error) })
|
||||
this.loggerService.error('ntfy client failed', { error })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@ export class NtfyProvider implements OnApplicationBootstrap {
|
|||
async sendBootstrappedNotification() {
|
||||
const version =
|
||||
this.configService.get<string>('GIT_COMMIT') ??
|
||||
(this.configService.get('LOCAL') === 'true' ? 'LOCAL' : 'unknown')
|
||||
this.configService.get('LOCAL') === 'true'
|
||||
? 'LOCAL'
|
||||
: 'unknown'
|
||||
await this.publishQueue.add(
|
||||
...NtfyProvider.defaultJob({
|
||||
title: 'Autobaan up and running',
|
||||
|
|
@ -105,6 +107,39 @@ export class NtfyProvider implements OnApplicationBootstrap {
|
|||
)
|
||||
}
|
||||
|
||||
async sendPerformingRiskyReservationNotification(
|
||||
reservationId: string,
|
||||
startTime: Dayjs,
|
||||
endTime: Dayjs,
|
||||
) {
|
||||
const url = `${this.configService.get(
|
||||
'BASE_URL',
|
||||
)}/reservations/${reservationId}`
|
||||
await this.publishQueue.add(
|
||||
...NtfyProvider.defaultJob({
|
||||
title: 'Handling risky reservation. Waiting for confirmation',
|
||||
message: `Waiting for ${reservationId} - ${startTime.format()} to ${endTime.format()}`,
|
||||
actions: [
|
||||
{
|
||||
action: 'http',
|
||||
label: 'Accept',
|
||||
url: `${url}/resume`,
|
||||
method: 'POST',
|
||||
clear: true,
|
||||
},
|
||||
{
|
||||
action: 'http',
|
||||
label: 'Reject',
|
||||
url,
|
||||
method: 'DELETE',
|
||||
clear: true,
|
||||
},
|
||||
],
|
||||
tags: [MessageTags.warning, MessageTags.passport_control],
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async sendErrorPerformingReservationNotification(
|
||||
reservationId: string,
|
||||
startTime: Dayjs,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export enum MessageTags {
|
|||
clock11 = 'clock11',
|
||||
clock1130 = 'clock1130',
|
||||
badminton = 'badminton',
|
||||
passport_control = 'passport_control',
|
||||
}
|
||||
|
||||
export enum MessagePriority {
|
||||
|
|
@ -59,13 +60,60 @@ export enum MessagePriority {
|
|||
max = 5,
|
||||
}
|
||||
|
||||
type MessageActionType = 'broadcast' | 'copy' | 'http' | 'view'
|
||||
|
||||
export interface MessageActionConfig {
|
||||
action: MessageActionType
|
||||
/**
|
||||
* Label displayed on the action button
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Clear notification after action button is tapped
|
||||
*/
|
||||
clear?: boolean
|
||||
}
|
||||
|
||||
export interface BroadcastActionConfig extends MessageActionConfig {
|
||||
action: 'broadcast'
|
||||
intent?: string
|
||||
extras?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface CopyActionConfig extends MessageActionConfig {
|
||||
action: 'copy'
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface HttpActionConfig extends MessageActionConfig {
|
||||
action: 'http'
|
||||
url: string
|
||||
/**
|
||||
* @default 'POST'
|
||||
*/
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' // there are more but if I use them I'll have gone insane
|
||||
headers?: Record<string, string>
|
||||
body?: string
|
||||
}
|
||||
export interface ViewActionConfig extends MessageActionConfig {
|
||||
action: 'view'
|
||||
url: string
|
||||
clear?: boolean
|
||||
}
|
||||
|
||||
export type MessageAction =
|
||||
| BroadcastActionConfig
|
||||
| CopyActionConfig
|
||||
| HttpActionConfig
|
||||
| ViewActionConfig
|
||||
|
||||
export interface MessageConfig {
|
||||
topic: string
|
||||
message?: string
|
||||
title?: string
|
||||
tags?: MessageTags[]
|
||||
priority?: MessagePriority
|
||||
actions?: object[]
|
||||
actions?: MessageAction[]
|
||||
markdown?: boolean
|
||||
icon?: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
import { Controller, Get, Inject } from '@nestjs/common'
|
||||
|
||||
import { PrometheusProvider } from './provider'
|
||||
|
||||
@Controller('metrics')
|
||||
export class PrometheusController {
|
||||
constructor(
|
||||
@Inject(PrometheusProvider)
|
||||
private readonly promProvider: PrometheusProvider,
|
||||
) {}
|
||||
|
||||
@Get('/')
|
||||
async getMetrics() {
|
||||
return await this.promProvider.getMetrics()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Module } from '@nestjs/common'
|
||||
|
||||
import { PrometheusController } from './controller'
|
||||
import { PrometheusProvider } from './provider'
|
||||
|
||||
@Module({
|
||||
controllers: [PrometheusController],
|
||||
providers: [PrometheusProvider],
|
||||
})
|
||||
export class PrometheusModule {}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { OnApplicationBootstrap } from '@nestjs/common'
|
||||
import { collectDefaultMetrics, Counter, Registry } from 'prom-client'
|
||||
|
||||
export class PrometheusProvider implements OnApplicationBootstrap {
|
||||
private readonly registry: Registry
|
||||
private readonly counters: Map<string, Counter>
|
||||
|
||||
constructor() {
|
||||
this.registry = new Registry()
|
||||
this.counters = new Map()
|
||||
}
|
||||
|
||||
onApplicationBootstrap() {
|
||||
collectDefaultMetrics({
|
||||
register: this.registry,
|
||||
prefix: 'autobaan_',
|
||||
labels: { GIT_COMMIT: process.env.GIT_COMMIT ?? 'unknown' },
|
||||
})
|
||||
}
|
||||
|
||||
async getMetrics() {
|
||||
return this.registry.metrics()
|
||||
}
|
||||
|
||||
async count(name: string, help: string, increment = 1) {
|
||||
let counter = this.counters.get(name)
|
||||
if (counter == null) {
|
||||
counter = new Counter({ name, help })
|
||||
this.counters.set(name, counter)
|
||||
}
|
||||
|
||||
counter.inc(increment)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import {
|
|||
ValidateNested,
|
||||
} from 'class-validator'
|
||||
|
||||
import { CustomResponseTransformInterceptor } from '../common/customResponse'
|
||||
import { DayOfWeek } from './entity'
|
||||
import { RecurringReservationsService } from './service'
|
||||
|
||||
|
|
@ -80,7 +79,7 @@ export class UpdateRecurringReservationRequest {
|
|||
}
|
||||
|
||||
@Controller('recurring-reservations')
|
||||
@UseInterceptors(ClassSerializerInterceptor, CustomResponseTransformInterceptor)
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
export class RecurringReservationsController {
|
||||
constructor(
|
||||
@Inject(RecurringReservationsService)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import {
|
|||
} from 'class-validator'
|
||||
import { Dayjs } from 'dayjs'
|
||||
|
||||
import { CustomResponseTransformInterceptor } from '../common/customResponse'
|
||||
import { DayjsTransformer } from '../common/dayjs'
|
||||
import { LoggerService } from '../logger/service.logger'
|
||||
import { RESERVATIONS_QUEUE_NAME, ReservationsQueue } from './config'
|
||||
|
|
@ -71,7 +70,7 @@ export class CreateReservationRequest {
|
|||
}
|
||||
|
||||
@Controller('reservations')
|
||||
@UseInterceptors(ClassSerializerInterceptor, CustomResponseTransformInterceptor)
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
export class ReservationsController {
|
||||
constructor(
|
||||
@Inject(ReservationsService)
|
||||
|
|
|
|||
|
|
@ -97,9 +97,7 @@ export class ReservationsCronService {
|
|||
await this.ntfyProvider.sendCronStartNotification(
|
||||
'cleanUpExpiredReservations',
|
||||
)
|
||||
const reservations = await this.reservationService.getOlderThanDate(
|
||||
dayjs().subtract(7, 'day'),
|
||||
)
|
||||
const reservations = await this.reservationService.getByDate()
|
||||
this.loggerService.debug(
|
||||
`Found ${reservations.length} reservations to delete`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ import { LoggerService } from '../logger/service.logger'
|
|||
import { BaanReserverenService } from '../runner/baanreserveren/service'
|
||||
import { Reservation, ReservationStatus } from './entity'
|
||||
|
||||
export enum ReservationDangerLevel {
|
||||
Safe = 'safe',
|
||||
Risky = 'risky',
|
||||
Lethal = 'lethal',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ReservationsService {
|
||||
constructor(
|
||||
|
|
@ -41,17 +47,6 @@ export class ReservationsService {
|
|||
return await qb.orderBy('dateRangeStart', 'ASC').getMany()
|
||||
}
|
||||
|
||||
async getOlderThanDate(date = dayjs()) {
|
||||
const query = this.reservationsRepository
|
||||
.createQueryBuilder()
|
||||
.where(`DATE(dateRangeStart) < DATE(:startDate)`, {
|
||||
startDate: date.toISOString(),
|
||||
})
|
||||
.orderBy('dateRangeStart', 'ASC')
|
||||
|
||||
return await query.getMany()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all reservations that have not been scheduled that are within the reservation window
|
||||
* @returns Reservations that can be scheduled
|
||||
|
|
@ -104,6 +99,19 @@ export class ReservationsService {
|
|||
return await this.reservationsRepository.save(res)
|
||||
}
|
||||
|
||||
getDangerLevel(reservation: Reservation) {
|
||||
// don't book something within the ~~danger zone~~
|
||||
const now = dayjs()
|
||||
const hourDiff = now.diff(reservation.dateRangeStart, 'hours')
|
||||
if (hourDiff > 8) {
|
||||
return ReservationDangerLevel.Safe
|
||||
} else if (hourDiff >= 5) {
|
||||
return ReservationDangerLevel.Risky
|
||||
} else {
|
||||
return ReservationDangerLevel.Lethal
|
||||
}
|
||||
}
|
||||
|
||||
async update(reservationId: string, update: Partial<Reservation>) {
|
||||
return await this.reservationsRepository.update(reservationId, update)
|
||||
}
|
||||
|
|
@ -112,14 +120,10 @@ export class ReservationsService {
|
|||
const reservation = await this.getById(id)
|
||||
if (!reservation) return
|
||||
|
||||
try {
|
||||
if (reservation.status === ReservationStatus.Booked) {
|
||||
if (reservation.status === ReservationStatus.Booked)
|
||||
await this.brService.cancelReservation(reservation)
|
||||
} else if (reservation.status === ReservationStatus.OnWaitingList) {
|
||||
else if (reservation.status === ReservationStatus.OnWaitingList)
|
||||
await this.brService.removeReservationFromWaitList(reservation)
|
||||
}
|
||||
} catch (error: unknown) {}
|
||||
|
||||
return await this.reservationsRepository.delete({ id: reservation.id })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,8 +119,7 @@ export const StartTimeClassCourtSlots: Record<
|
|||
} as const
|
||||
|
||||
const StartTimeClassStartTimes = {
|
||||
[StartTimeClass.First]: {
|
||||
weekday: [
|
||||
[StartTimeClass.First]: [
|
||||
'07:15',
|
||||
'08:00',
|
||||
'08:45',
|
||||
|
|
@ -143,25 +142,7 @@ const StartTimeClassStartTimes = {
|
|||
'21:30',
|
||||
'22:15',
|
||||
],
|
||||
weekend: [
|
||||
'09:00',
|
||||
'09:45',
|
||||
'10:30',
|
||||
'11:15',
|
||||
'12:00',
|
||||
'12:45',
|
||||
'13:30',
|
||||
'14:15',
|
||||
'15:00',
|
||||
'15:45',
|
||||
'16:30',
|
||||
'17:15',
|
||||
'18:00',
|
||||
'18:45',
|
||||
],
|
||||
},
|
||||
[StartTimeClass.Second]: {
|
||||
weekday: [
|
||||
[StartTimeClass.Second]: [
|
||||
'07:45',
|
||||
'08:30',
|
||||
'09:15',
|
||||
|
|
@ -183,25 +164,7 @@ const StartTimeClassStartTimes = {
|
|||
'21:15',
|
||||
'22:00',
|
||||
],
|
||||
weekend: [
|
||||
'09:15',
|
||||
'10:00',
|
||||
'10:45',
|
||||
'11:30',
|
||||
'12:15',
|
||||
'13:00',
|
||||
'13:45',
|
||||
'14:30',
|
||||
'15:15',
|
||||
'16:00',
|
||||
'16:45',
|
||||
'17:30',
|
||||
'18:15',
|
||||
'19:00',
|
||||
],
|
||||
},
|
||||
[StartTimeClass.Third]: {
|
||||
weekday: [
|
||||
[StartTimeClass.Third]: [
|
||||
'07:30',
|
||||
'08:15',
|
||||
'09:00',
|
||||
|
|
@ -224,23 +187,6 @@ const StartTimeClassStartTimes = {
|
|||
'21:45',
|
||||
'22:30',
|
||||
],
|
||||
weekend: [
|
||||
'08:45',
|
||||
'09:30',
|
||||
'10:15',
|
||||
'11:00',
|
||||
'11:45',
|
||||
'12:30',
|
||||
'13:15',
|
||||
'14:00',
|
||||
'14:45',
|
||||
'15:30',
|
||||
'16:15',
|
||||
'17:00',
|
||||
'17:45',
|
||||
'18:30',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const TYPING_DELAY_MS = 2
|
||||
|
|
@ -793,12 +739,7 @@ export class BaanReserverenService {
|
|||
|
||||
public getCourtSlotsForDate(date: Dayjs) {
|
||||
const time = date.format('HH:mm')
|
||||
const weekdayEndSelector =
|
||||
date.day() !== 0 || date.day() !== 6 ? 'weekday' : 'weekend'
|
||||
for (const [timeClass, timesForWeekdayAndWeekend] of Object.entries(
|
||||
StartTimeClassStartTimes,
|
||||
)) {
|
||||
const times = timesForWeekdayAndWeekend[weekdayEndSelector]
|
||||
for (const [timeClass, times] of Object.entries(StartTimeClassStartTimes)) {
|
||||
if (times.includes(time)) {
|
||||
const courtSlots = [
|
||||
...StartTimeClassCourtSlots[timeClass as StartTimeClass],
|
||||
|
|
@ -971,12 +912,8 @@ export class BaanReserverenService {
|
|||
try {
|
||||
currentAttempt++
|
||||
await this.init()
|
||||
break
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof RunningReservationsNavigationError) {
|
||||
this.loggerService.warn('Hit expected warmup error, retrying', {
|
||||
currentAttempt,
|
||||
})
|
||||
await new Promise((res) => setTimeout(res, delay))
|
||||
} else {
|
||||
throw err
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ import {
|
|||
RESERVATIONS_QUEUE_NAME,
|
||||
ReservationsQueue,
|
||||
} from '../reservations/config'
|
||||
import { ReservationsService } from '../reservations/service'
|
||||
import { Reservation } from '../reservations/entity'
|
||||
import {
|
||||
ReservationDangerLevel,
|
||||
ReservationsService,
|
||||
} from '../reservations/service'
|
||||
import { WaitingListDetails } from './types'
|
||||
|
||||
const EMAIL_SUBJECT_REGEX = new RegExp(
|
||||
|
|
@ -90,12 +94,45 @@ export class WaitingListService {
|
|||
return
|
||||
}
|
||||
|
||||
// don't book something within the ~~danger zone~~
|
||||
const partitioned = reservations.reduce<{
|
||||
safe: Reservation[]
|
||||
risky: Reservation[]
|
||||
lethal: Reservation[]
|
||||
}>(
|
||||
(acc, res) => {
|
||||
switch (this.reservationsService.getDangerLevel(res)) {
|
||||
case ReservationDangerLevel.Safe:
|
||||
return {
|
||||
safe: [...acc.safe, res],
|
||||
risky: acc.risky,
|
||||
lethal: acc.lethal,
|
||||
}
|
||||
case ReservationDangerLevel.Risky:
|
||||
return {
|
||||
safe: acc.safe,
|
||||
risky: [...acc.risky, res],
|
||||
lethal: acc.lethal,
|
||||
}
|
||||
case ReservationDangerLevel.Lethal:
|
||||
return {
|
||||
safe: acc.safe,
|
||||
risky: acc.risky,
|
||||
lethal: [...acc.lethal, res],
|
||||
}
|
||||
}
|
||||
},
|
||||
{ safe: [], risky: [], lethal: [] },
|
||||
)
|
||||
|
||||
this.loggerService.debug(
|
||||
`Found ${reservations.length} reservations on waiting list`,
|
||||
`Found the reservations in given categories: ${JSON.stringify(
|
||||
partitioned,
|
||||
)}`,
|
||||
)
|
||||
|
||||
await this.reservationsQueue.addBulk(
|
||||
reservations.map((r) => ({
|
||||
partitioned.safe.map((r) => ({
|
||||
data: { reservation: r, speedyMode: false },
|
||||
opts: { attempts: 1 },
|
||||
})),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue