Adding a warmup and changing daily reservations to utilize this warmup + busy loop to be faster in the morning

This commit is contained in:
Collin Duncan 2024-04-09 23:19:57 +02:00
parent 73b32402d3
commit e7bff228c6
No known key found for this signature in database
6 changed files with 86 additions and 52 deletions

View file

@ -24,6 +24,8 @@ export interface SerializedDateRange {
end: string end: string
} }
export const setDefaults = () => dayjs.tz.setDefault('Europe/Amsterdam')
export const convertDateRangeStringToObject = ({ export const convertDateRangeStringToObject = ({
start, start,
end, end,

View file

@ -4,6 +4,7 @@ import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module' import { AppModule } from './app.module'
import { CustomResponseTransformInterceptor } from './common/customResponse' import { CustomResponseTransformInterceptor } from './common/customResponse'
import { setDefaults } from './common/dayjs'
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule, { abortOnError: false }) const app = await NestFactory.create(AppModule, { abortOnError: false })
@ -12,6 +13,7 @@ async function bootstrap() {
app.enableShutdownHooks() app.enableShutdownHooks()
app.useGlobalPipes(new ValidationPipe({ transform: true })) app.useGlobalPipes(new ValidationPipe({ transform: true }))
app.useGlobalInterceptors(new CustomResponseTransformInterceptor()) app.useGlobalInterceptors(new CustomResponseTransformInterceptor())
setDefaults()
await app.listen(port, () => console.log(`Listening on port ${port}`)) await app.listen(port, () => console.log(`Listening on port ${port}`))
} }

View file

@ -1,8 +1,10 @@
import { repl } from '@nestjs/core' import { repl } from '@nestjs/core'
import { AppModule } from './app.module' import { AppModule } from './app.module'
import { setDefaults } from './common/dayjs'
async function bootstrap() { async function bootstrap() {
setDefaults()
await repl(AppModule) await repl(AppModule)
} }
bootstrap() bootstrap()

View file

@ -30,7 +30,7 @@ export class ReservationsCronService {
private readonly loggerService: LoggerService, private readonly loggerService: LoggerService,
) {} ) {}
@Cron(CronExpression.EVERY_DAY_AT_7AM, { @Cron('55 06 * * *', {
name: 'handleDailyReservations', name: 'handleDailyReservations',
timeZone: 'Europe/Amsterdam', timeZone: 'Europe/Amsterdam',
}) })
@ -42,12 +42,36 @@ export class ReservationsCronService {
this.loggerService.log( this.loggerService.log(
`Found ${reservationsToPerform.length} reservations to perform`, `Found ${reservationsToPerform.length} reservations to perform`,
) )
await this.reservationsQueue.addBulk(
reservationsToPerform.map((r) => ({ // In order to make sure session is fresh and speed up some shit let's warm him up
data: r, await this.brService.warmup()
opts: { attempts: DAILY_RESERVATIONS_ATTEMPTS },
})), this.loggerService.log(`Warmed up! Waiting for go-time`)
let not7AM = true
const waitTime = 10
const time7AM = dayjs()
.set('hour', 7)
.set('minute', 0)
.set('second', 0)
.set('millisecond', 0)
while (not7AM) {
not7AM = time7AM.isBefore(dayjs()) && time7AM.diff(dayjs()) >= waitTime // current time is more than 100ms from 7am
if (!not7AM) break
await new Promise((res) => setTimeout(res, waitTime)) // wait for waitTime and then try again
}
this.loggerService.log(`It's go-time`)
for (const res of reservationsToPerform) {
await this.brService.performReservation(res).catch(
async () =>
await this.reservationsQueue.add(res, {
attempts: Math.max(DAILY_RESERVATIONS_ATTEMPTS - 1, 1),
}),
) )
}
} else { } else {
this.loggerService.log('Monitoring reservations') this.loggerService.log('Monitoring reservations')
await this.brService.monitorCourtReservations(dayjs().add(7, 'day')) await this.brService.monitorCourtReservations(dayjs().add(7, 'day'))

View file

@ -51,7 +51,7 @@ export class ReservationsService {
}, },
) )
.andWhere(`waitListed = false`) .andWhere(`waitListed = false`)
.orderBy('dateRangeStart', 'DESC') .orderBy('dateRangeStart', 'ASC')
return await query.getMany() return await query.getMany()
} }

View file

@ -82,7 +82,7 @@ const CourtRank = {
[CourtSlot.Thirteen]: 1, // no one likes upstairs [CourtSlot.Thirteen]: 1, // no one likes upstairs
} as const } as const
const TYPING_DELAY_MS = 5 const TYPING_DELAY_MS = 2
@Injectable() @Injectable()
export class BaanReserverenService { export class BaanReserverenService {
@ -127,14 +127,17 @@ export class BaanReserverenService {
) )
} }
// Check session by going to /reservations to see if we are still logged in via cookies
private async checkSession(username: string) { private async checkSession(username: string) {
this.loggerService.debug('Checking session', { this.loggerService.debug('Checking session', {
username, username,
session: this.session, session: this.session,
}) })
if (this.page.url().includes(BAAN_RESERVEREN_ROOT_URL)) {
// Check session by going to /reservations to see if we are still logged in via cookies if (!this.page.url().includes(BAAN_RESERVEREN_ROOT_URL)) {
await this.navigateToReservations() await this.navigateToReservations()
}
if (this.page.url().includes('?reason=LOGGED_IN')) { if (this.page.url().includes('?reason=LOGGED_IN')) {
return SessionAction.Login return SessionAction.Login
} }
@ -143,8 +146,6 @@ export class BaanReserverenService {
? SessionAction.Logout ? SessionAction.Logout
: SessionAction.NoAction : SessionAction.NoAction
} }
return SessionAction.Login
}
private startSession(username: string) { private startSession(username: string) {
this.loggerService.debug('Starting session', { username }) this.loggerService.debug('Starting session', { username })
@ -202,8 +203,7 @@ export class BaanReserverenService {
private async init() { private async init() {
this.loggerService.debug('Initializing') this.loggerService.debug('Initializing')
await this.page.goto(BAAN_RESERVEREN_ROOT_URL) await this.navigateToReservations()
await this.page.waitForNetworkIdle()
const action = await this.checkSession(this.username) const action = await this.checkSession(this.username)
switch (action) { switch (action) {
case SessionAction.Logout: case SessionAction.Logout:
@ -533,6 +533,37 @@ export class BaanReserverenService {
await this.page.waitForNetworkIdle() await this.page.waitForNetworkIdle()
} }
private async getAllCourtStatuses() {
const courts = await this.page.$$('tr > td.slot')
const courtStatuses: {
courtNumber: string
startTime: string
status: string
duration: string
}[] = []
for (const court of courts) {
const classListObj = await (
await court.getProperty('classList')
).jsonValue()
const classList = Object.values(classListObj)
const rClass = classList.filter((cl) => /r-\d{2}/.test(cl))[0]
const courtNumber =
`${CourtSlotToNumber[rClass.replace(/r-/, '') as CourtSlot]}` ??
'unknown court'
const startTime = await court
.$eval('div.slot-period', (e) => e.innerText.trim())
.catch(() => 'unknown')
const status = classList.includes('free') ? 'available' : 'unavailable'
const courtRowSpan = await (
await court.getProperty('rowSpan')
).jsonValue()
const duration = `${Number(courtRowSpan ?? '0') * 15} minutes`
courtStatuses.push({ courtNumber, startTime, status, duration }) //const d = require('dayjs'); await get(BaanReserverenService).monitorCourtReservations(d());
}
return courtStatuses
}
public async performReservation(reservation: Reservation) { public async performReservation(reservation: Reservation) {
try { try {
await this.init() await this.init()
@ -588,37 +619,6 @@ export class BaanReserverenService {
} }
} }
private async getAllCourtStatuses() {
const courts = await this.page.$$('tr > td.slot')
const courtStatuses: {
courtNumber: string
startTime: string
status: string
duration: string
}[] = []
for (const court of courts) {
const classListObj = await (
await court.getProperty('classList')
).jsonValue()
const classList = Object.values(classListObj)
const rClass = classList.filter((cl) => /r-\d{2}/.test(cl))[0]
const courtNumber =
`${CourtSlotToNumber[rClass.replace(/r-/, '') as CourtSlot]}` ??
'unknown court'
const startTime = await court
.$eval('div.slot-period', (e) => e.innerText.trim())
.catch(() => 'unknown')
const status = classList.includes('free') ? 'available' : 'unavailable'
const courtRowSpan = await (
await court.getProperty('rowSpan')
).jsonValue()
const duration = `${Number(courtRowSpan ?? '0') * 15} minutes`
courtStatuses.push({ courtNumber, startTime, status, duration }) //const d = require('dayjs'); await get(BaanReserverenService).monitorCourtReservations(d());
}
return courtStatuses
}
public async monitorCourtReservations(date?: Dayjs, swallowError = true) { public async monitorCourtReservations(date?: Dayjs, swallowError = true) {
try { try {
if (date) { if (date) {
@ -642,6 +642,10 @@ export class BaanReserverenService {
} }
} }
} }
public async warmup() {
await this.init()
}
} }
export class RunnerError extends Error { export class RunnerError extends Error {