WIP commit for handling risky reservations

This commit is contained in:
collin 2026-04-10 17:43:59 +02:00
parent bedd062aa0
commit f99550d879
No known key found for this signature in database
4 changed files with 135 additions and 12 deletions

View file

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

View file

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

View file

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

View file

@ -13,7 +13,10 @@ import {
ReservationsQueue,
} from '../reservations/config'
import { Reservation } from '../reservations/entity'
import { ReservationsService } from '../reservations/service'
import {
ReservationDangerLevel,
ReservationsService,
} from '../reservations/service'
import { WaitingListDetails } from './types'
const EMAIL_SUBJECT_REGEX = new RegExp(
@ -92,24 +95,44 @@ export class WaitingListService {
}
// don't book something within the ~~danger zone~~
const now = dayjs()
const { safe, dangerous } = reservations.reduce<{
const partitioned = reservations.reduce<{
safe: Reservation[]
dangerous: Reservation[]
risky: Reservation[]
lethal: Reservation[]
}>(
(acc, res) =>
now.diff(res.dateRangeStart, 'hours') > 5
? { safe: [...acc.safe, res], dangerous: acc.dangerous }
: { safe: acc.safe, dangerous: [...acc.dangerous, res] },
{ safe: [], dangerous: [] },
(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 ${safe.length} safe reservations on waiting list (${dangerous.length} dangerous)`,
`Found the reservations in given categories: ${JSON.stringify(
partitioned,
)}`,
)
await this.reservationsQueue.addBulk(
safe.map((r) => ({
partitioned.safe.map((r) => ({
data: { reservation: r, speedyMode: false },
opts: { attempts: 1 },
})),