Compare commits

...
Sign in to create a new pull request.

3 commits

4 changed files with 147 additions and 6 deletions

View file

@ -60,11 +60,15 @@ export class NtfyProvider implements OnApplicationBootstrap {
} }
async sendBootstrappedNotification() { async sendBootstrappedNotification() {
const gitCommit = this.configService.get<string>('GIT_COMMIT') const version =
this.configService.get<string>('GIT_COMMIT') ??
this.configService.get('LOCAL') === 'true'
? 'LOCAL'
: 'unknown'
await this.publishQueue.add( await this.publishQueue.add(
...NtfyProvider.defaultJob({ ...NtfyProvider.defaultJob({
title: 'Autobaan up and running', title: 'Autobaan up and running',
message: `Version ${gitCommit}`, message: `Version ${version}`,
tags: [MessageTags.badminton], tags: [MessageTags.badminton],
}), }),
) )
@ -103,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( async sendErrorPerformingReservationNotification(
reservationId: string, reservationId: string,
startTime: Dayjs, startTime: Dayjs,

View file

@ -49,6 +49,7 @@ export enum MessageTags {
clock11 = 'clock11', clock11 = 'clock11',
clock1130 = 'clock1130', clock1130 = 'clock1130',
badminton = 'badminton', badminton = 'badminton',
passport_control = 'passport_control',
} }
export enum MessagePriority { export enum MessagePriority {
@ -59,13 +60,60 @@ export enum MessagePriority {
max = 5, 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 { export interface MessageConfig {
topic: string topic: string
message?: string message?: string
title?: string title?: string
tags?: MessageTags[] tags?: MessageTags[]
priority?: MessagePriority priority?: MessagePriority
actions?: object[] actions?: MessageAction[]
markdown?: boolean markdown?: boolean
icon?: string icon?: string
} }

View file

@ -8,6 +8,12 @@ import { LoggerService } from '../logger/service.logger'
import { BaanReserverenService } from '../runner/baanreserveren/service' import { BaanReserverenService } from '../runner/baanreserveren/service'
import { Reservation, ReservationStatus } from './entity' import { Reservation, ReservationStatus } from './entity'
export enum ReservationDangerLevel {
Safe = 'safe',
Risky = 'risky',
Lethal = 'lethal',
}
@Injectable() @Injectable()
export class ReservationsService { export class ReservationsService {
constructor( constructor(
@ -93,6 +99,19 @@ export class ReservationsService {
return await this.reservationsRepository.save(res) 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>) { async update(reservationId: string, update: Partial<Reservation>) {
return await this.reservationsRepository.update(reservationId, update) return await this.reservationsRepository.update(reservationId, update)
} }

View file

@ -12,7 +12,11 @@ import {
RESERVATIONS_QUEUE_NAME, RESERVATIONS_QUEUE_NAME,
ReservationsQueue, ReservationsQueue,
} from '../reservations/config' } from '../reservations/config'
import { ReservationsService } from '../reservations/service' import { Reservation } from '../reservations/entity'
import {
ReservationDangerLevel,
ReservationsService,
} from '../reservations/service'
import { WaitingListDetails } from './types' import { WaitingListDetails } from './types'
const EMAIL_SUBJECT_REGEX = new RegExp( const EMAIL_SUBJECT_REGEX = new RegExp(
@ -90,12 +94,45 @@ export class WaitingListService {
return 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( this.loggerService.debug(
`Found ${reservations.length} reservations on waiting list`, `Found the reservations in given categories: ${JSON.stringify(
partitioned,
)}`,
) )
await this.reservationsQueue.addBulk( await this.reservationsQueue.addBulk(
reservations.map((r) => ({ partitioned.safe.map((r) => ({
data: { reservation: r, speedyMode: false }, data: { reservation: r, speedyMode: false },
opts: { attempts: 1 }, opts: { attempts: 1 },
})), })),