Updating errors and logging in a lot of places. Changing some static methods of Reservation to instance methods
This commit is contained in:
parent
983c785029
commit
6dfa37272e
7 changed files with 123 additions and 59 deletions
|
|
@ -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}`
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
@ -25,14 +25,16 @@ export const reserve = async (reservation?: Reservation): Promise<boolean> => {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) })
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerReservationConfirmSubmitError(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class LoggableError extends Error {
|
||||
toString() {
|
||||
return `${this.name} - ${this.message}\n${this.stack}`
|
||||
}
|
||||
}
|
||||
export class RunnerError extends LoggableError {
|
||||
constructor(error: Error) {
|
||||
super(error.message)
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -12,28 +12,29 @@ const getTaskConfig = (name: string): ScheduleOptions => ({
|
|||
})
|
||||
|
||||
const logger = new Logger('cron', 'default', LogLevel.DEBUG)
|
||||
let shouldContinue = true
|
||||
|
||||
export const startTasks = () => {
|
||||
try {
|
||||
if (tasks.length === 0) {
|
||||
const task = schedule(
|
||||
'*/10 7 * * *',
|
||||
'* 7 * * *',
|
||||
async (timestamp) => {
|
||||
asyncLocalStorage.run(
|
||||
new Logger('cron', v4(), LogLevel.DEBUG),
|
||||
async () => {
|
||||
if (shouldContinue) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -42,16 +43,19 @@ export const startTasks = () => {
|
|||
)
|
||||
logger.debug('Started cron task')
|
||||
tasks.push(task)
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to start tasks', { error: error.message })
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to start tasks', { error: (error as Error).message })
|
||||
}
|
||||
}
|
||||
|
||||
export const stopTasks = () => {
|
||||
try {
|
||||
if (tasks.length > 0) {
|
||||
tasks.map((task) => task.stop())
|
||||
logger.debug('Stopped cron tasks')
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to stop tasks', { error: error.message })
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to stop tasks', { error: (error as Error).message })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue