Updating errors and logging in a lot of places. Changing some static methods of Reservation to instance methods

This commit is contained in:
Collin Duncan 2023-01-30 12:38:42 +01:00
parent 983c785029
commit 6dfa37272e
No known key found for this signature in database
7 changed files with 123 additions and 59 deletions

View file

@ -85,3 +85,9 @@ export class Logger {
this.log(LogLevel.ERROR, message, details)
}
}
export class LoggableError extends Error {
toString() {
return `${this.name} - ${this.message}\n${this.stack}`
}
}

View file

@ -76,15 +76,16 @@ export class Reservation {
.subtract(RESERVATION_AVAILABLE_WITHIN_DAYS, 'days')
}
public toString() {
return JSON.stringify(this.format())
public toString(obfuscate = false) {
return JSON.stringify(this.format(obfuscate))
}
public format() {
public format(obfuscate = false) {
return {
id: this.id,
user: {
username: this.user.username,
password: this.user.password,
password: obfuscate ? '???' : this.user.password,
},
opponent: this.opponent,
booked: this.booked,
@ -98,6 +99,7 @@ export class Reservation {
public serializeToJson(): SerializedReservation {
return {
id: this.id,
user: this.user,
opponent: this.opponent,
booked: this.booked,
@ -126,7 +128,7 @@ export class Reservation {
return dates.map((date) => dayjs(date))
}
public static async save(res: Reservation) {
public async save() {
await run(
`
INSERT INTO reservations
@ -151,24 +153,34 @@ export class Reservation {
)
`,
[
res.id,
res.user.username,
res.user.password,
res.dateRange.start.format('YYYY-MM-DD HH:mm:ss'),
res.dateRange.end.format('YYYY-MM-DD HH:mm:ss'),
res.opponent.id,
res.opponent.name,
this.id,
this.user.username,
this.user.password,
this.dateRange.start.format('YYYY-MM-DD HH:mm:ss'),
this.dateRange.end.format('YYYY-MM-DD HH:mm:ss'),
this.opponent.id,
this.opponent.name,
]
)
}
public static async delete(res: Reservation) {
public async delete() {
await run(
`
DELETE FROM reservations
WHERE id = ?
`,
[res.id]
[this.id]
)
}
public static async deleteById(id: string) {
await run(
`
DELETE FROM reservations
WHERE id = ?
`,
[id]
)
}
@ -273,6 +285,43 @@ export class Reservation {
return []
}
public static async fetchByPage(pageNumber: number, pageSize = 50): Promise<Reservation[]> {
const response = await all<SqlReservation>(
`
SELECT *
FROM reservations
ORDER BY date_range_start ASC
LIMIT ?
OFFSET ?;
`,
[pageSize, pageSize * pageNumber]
)
if (response.length > 0) {
return response.map(
(sqlReservation) =>
new Reservation(
{
username: sqlReservation.username,
password: sqlReservation.password,
},
{
start: dayjs(sqlReservation.date_range_start),
end: dayjs(sqlReservation.date_range_end),
},
{
id: sqlReservation.opponent_id,
name: sqlReservation.opponent_name,
},
undefined,
sqlReservation.id
)
)
}
return []
}
}
export interface SerializedDateRange {
@ -281,6 +330,7 @@ export interface SerializedDateRange {
}
export interface SerializedReservation {
id: string
user: User
opponent: Opponent
booked: boolean

View file

@ -1,6 +1,6 @@
import { asyncLocalStorage as l } from './logger'
import { asyncLocalStorage as l, LoggableError } from './logger'
import { Reservation } from './reservation'
import { LoggableError, Runner } from './runner'
import { Runner } from './runner'
let runner: Runner | undefined
const getRunner = () => {
@ -24,15 +24,17 @@ export const reserve = async (reservation?: Reservation): Promise<boolean> => {
l.getStore()?.info('No reservation to perform')
return true
}
l.getStore()?.debug('Trying to perform reservation', { reservationToPerform })
l.getStore()?.debug('Trying to perform reservation', { reservationToPerform: reservationToPerform.toString(true) })
const runner = getRunner()
try {
await runner.run(reservationToPerform)
await Reservation.delete(reservationToPerform)
await reservationToPerform.delete()
return true
} catch (error) {
l.getStore()?.error('Failed to perform reservation', { error: (error as LoggableError).toString() })
l.getStore()?.error('Failed to perform reservation', {
error: (error as LoggableError).toString(),
})
return false
}
}

View file

@ -8,7 +8,7 @@ import puppeteer, {
LaunchOptions,
Page,
} from 'puppeteer'
import { asyncLocalStorage as l } from './logger'
import { asyncLocalStorage as l, LoggableError } from './logger'
import { Opponent, Reservation } from './reservation'
export class Runner {
@ -130,7 +130,7 @@ export class Runner {
}
private async selectAvailableTime(res: Reservation): Promise<void> {
l.getStore()?.debug('Selecting available time', res.format())
l.getStore()?.debug('Selecting available time', { reservation: res.toString(true) })
let freeCourt: ElementHandle | null | undefined
let i = 0
while (i < res.possibleDates.length && !freeCourt) {
@ -152,7 +152,7 @@ export class Runner {
}
private async selectOpponent(opponent: Opponent): Promise<void> {
l.getStore()?.debug('Selecting opponent', opponent)
l.getStore()?.debug('Selecting opponent', { opponent })
const player2Search = await this.page
?.waitForSelector('tr.res-make-player-2 > td > input')
.catch((e: Error) => {
@ -173,20 +173,21 @@ export class Runner {
}
private async confirmReservation(): Promise<void> {
await this.page?.$('input#__make_submit').then((b) => b?.click())
.catch((e: Error) => { throw new RunnerReservationConfirmButtonError(e) })
await this.page
?.$('input#__make_submit')
.then((b) => b?.click())
.catch((e: Error) => {
throw new RunnerReservationConfirmButtonError(e)
})
await this.page
?.waitForSelector('input#__make_submit2')
.then((b) => b?.click())
.catch((e: Error) => { throw new RunnerReservationConfirmSubmitError(e) })
}
}
export class LoggableError extends Error {
toString() {
return `${this.name} - ${this.message}\n${this.stack}`
.catch((e: Error) => {
throw new RunnerReservationConfirmSubmitError(e)
})
}
}
export class RunnerError extends LoggableError {
constructor(error: Error) {
super(error.message)
@ -215,4 +216,4 @@ export class RunnerOpponentSearchNetworkError extends RunnerError {}
export class RunnerOpponentSearchSelectionError extends RunnerError {}
export class RunnerReservationConfirmButtonError extends RunnerError {}
export class RunnerReservationConfirmSubmitError extends RunnerError {}
export class RunnerReservationConfirmSubmitError extends RunnerError {}

View file

@ -31,7 +31,7 @@ export const schedule = async (
}
logger?.debug('Successfully validated request', {
reservation: reservation.format(),
reservation: reservation.toString(true),
})
if (!reservation.isAvailableForReservation()) {
@ -39,7 +39,7 @@ export const schedule = async (
'Reservation date is more than 7 days away; saving for later reservation'
)
await Reservation.save(reservation)
await reservation.save()
return {
scheduledReservation: {

View file

@ -12,46 +12,50 @@ const getTaskConfig = (name: string): ScheduleOptions => ({
})
const logger = new Logger('cron', 'default', LogLevel.DEBUG)
let shouldContinue = true
export const startTasks = () => {
try {
const task = schedule(
'*/10 7 * * *',
async (timestamp) => {
asyncLocalStorage.run(
new Logger('cron', v4(), LogLevel.DEBUG),
async () => {
if (shouldContinue) {
if (tasks.length === 0) {
const task = schedule(
'* 7 * * *',
async (timestamp) => {
asyncLocalStorage.run(
new Logger('cron', v4(), LogLevel.DEBUG),
async () => {
const childLogger = asyncLocalStorage.getStore()
childLogger?.info('Running cron job', { timestamp })
try {
shouldContinue = await reserve()
const result = await reserve()
if (!result) {
throw new Error('Failed to complete reservation')
}
childLogger?.info('Completed running cron job')
} catch (error) {
shouldContinue = false
childLogger?.error('Error running cron job', {
error: (error as Error).message,
})
stopTasks()
}
}
}
)
},
getTaskConfig('reserver cron')
)
logger.debug('Started cron task')
tasks.push(task)
} catch (error: any) {
logger.error('Failed to start tasks', { error: error.message })
)
},
getTaskConfig('reserver cron')
)
logger.debug('Started cron task')
tasks.push(task)
}
} catch (error) {
logger.error('Failed to start tasks', { error: (error as Error).message })
}
}
export const stopTasks = () => {
try {
tasks.map((task) => task.stop())
logger.debug('Stopped cron tasks')
} catch (error: any) {
logger.error('Failed to stop tasks', { error: error.message })
if (tasks.length > 0) {
tasks.map((task) => task.stop())
logger.debug('Stopped cron tasks')
}
} catch (error) {
logger.error('Failed to stop tasks', { error: (error as Error).message })
}
}

View file

@ -5,6 +5,7 @@ import {
} from '../../../src/common/request'
import { Reservation } from '../../../src/common/reservation'
import { schedule, SchedulerInput } from '../../../src/common/scheduler'
import * as database from '../../../src/common/database'
jest.mock('../../../src/common/logger')
jest.mock('../../../src/common/reserver')
@ -13,7 +14,7 @@ jest.useFakeTimers().setSystemTime(new Date('2022-01-01'))
describe('scheduler', () => {
test('should handle valid requests within reservation window', async () => {
jest.spyOn(Reservation, 'save').mockResolvedValueOnce()
jest.spyOn(database, 'run').mockResolvedValueOnce()
const start = dayjs().add(15, 'minutes')
const end = start.add(15, 'minutes')