Updating runner to change to next month if needed; Added some more logging; Cleaning up some scripts

This commit is contained in:
Collin Duncan 2022-03-29 22:41:44 +02:00
parent 767a5930a6
commit 98bd540130
No known key found for this signature in database
12 changed files with 76 additions and 43 deletions

View file

@ -7,16 +7,15 @@
"node": "14.x" "node": "14.x"
}, },
"scripts": { "scripts": {
"build": "rm -r ./dist/* && rollup -c", "clean": "rm -r ./dist || true",
"build:reservationScheduler": "rm -r ./dist/reservationScheduler/ || : && rollup -c ./src/lambdas/reservationScheduler/rollup.config.js", "build:requester": "rollup -c src/workers/requester/rollup.config.js",
"package:reservationScheduler": "rm ./deploy/reservationScheduler.zip || : && mkdir ./deploy || : && zip deploy/reservationScheduler.zip -j dist/reservationScheduler/*", "build:scheduler": "rollup -c src/workers/scheduler/rollup.config.js",
"test": "jest", "test": "jest",
"test:clear-cache": "jest --clearCache", "test:clear-cache": "jest --clearCache",
"test:clean": "npm run test:clear-cache && npm run test", "test:clean": "npm run test:clear-cache && npm run test",
"lint": "eslint src/ --ext ts", "lint": "eslint src/ --ext ts",
"prettier": "prettier src -w", "prettier": "prettier src -w",
"local": "npx ts-node src/local.ts", "local": "npx ts-node src/local.ts"
"zip": "mkdir deploy && zip deploy/reservation-lambda.zip -r dist"
}, },
"author": "Collin Duncan <cgduncan7@gmail.com>", "author": "Collin Duncan <cgduncan7@gmail.com>",
"license": "ISC", "license": "ISC",

View file

@ -38,7 +38,11 @@ export class LoggerInstance {
private readonly correlationId: string private readonly correlationId: string
private readonly level: LogLevel private readonly level: LogLevel
public constructor(tag: string, correlationId: string, level = LogLevel.ERROR) { public constructor(
tag: string,
correlationId: string,
level = LogLevel.ERROR
) {
this.tag = tag this.tag = tag
this.correlationId = correlationId this.correlationId = correlationId
this.level = level this.level = level
@ -64,7 +68,12 @@ export class LoggerInstance {
} }
let fmtString = '<%s> [%s] %s: %s' let fmtString = '<%s> [%s] %s: %s'
const params: Array<unknown> = [this.tag, this.correlationId, levelString, message] const params: Array<unknown> = [
this.tag,
this.correlationId,
levelString,
message,
]
if (details) { if (details) {
params.push(details) params.push(details)
fmtString += ' - %O' fmtString += ' - %O'

View file

@ -157,10 +157,7 @@ const validateRequestOpponent = (opponent?: Opponent): void => {
const idRegex = /^-1$|^[^-]\d+$/ const idRegex = /^-1$|^[^-]\d+$/
const nameRegex = /^[A-Za-z0-9 -.'()]+$/ const nameRegex = /^[A-Za-z0-9 -.'()]+$/
const { id, name } = opponent const { id, name } = opponent
if ( if (!idRegex.test(id) || !nameRegex.test(name)) {
!idRegex.test(id) ||
!nameRegex.test(name)
) {
throw new ValidationError( throw new ValidationError(
'Invalid request', 'Invalid request',
ValidationErrorCode.INVALID_OPPONENT ValidationErrorCode.INVALID_OPPONENT

View file

@ -50,4 +50,16 @@ export class Reservation {
RESERVATION_AVAILABLE_WITHIN_DAYS RESERVATION_AVAILABLE_WITHIN_DAYS
) )
} }
public format(): unknown {
return {
opponent: this.opponent,
booked: this.booked,
possibleDates: this.possibleDates.map((date) => date.format()),
dateRange: {
start: this.dateRange.start.format(),
end: this.dateRange.end.format(),
}
}
}
} }

View file

@ -1,4 +1,4 @@
import { Dayjs } from 'dayjs' import dayjs, { Dayjs } from 'dayjs'
import puppeteer, { import puppeteer, {
Browser, Browser,
BrowserConnectOptions, BrowserConnectOptions,
@ -7,7 +7,7 @@ import puppeteer, {
LaunchOptions, LaunchOptions,
Page, Page,
} from 'puppeteer' } from 'puppeteer'
import { Logger } from './logger'
import { Opponent, Reservation } from './reservation' import { Opponent, Reservation } from './reservation'
export class Runner { export class Runner {
@ -33,6 +33,7 @@ export class Runner {
BrowserLaunchArgumentOptions & BrowserLaunchArgumentOptions &
BrowserConnectOptions BrowserConnectOptions
): Promise<Reservation[]> { ): Promise<Reservation[]> {
Logger.debug('Launching browser');
this.browser = await puppeteer.launch(options) this.browser = await puppeteer.launch(options)
this.page = await this.browser?.newPage() this.page = await this.browser?.newPage()
await this.login() await this.login()
@ -40,6 +41,7 @@ export class Runner {
} }
private async login() { private async login() {
Logger.debug('Logging in');
await this.page?.goto('https://squashcity.baanreserveren.nl/') await this.page?.goto('https://squashcity.baanreserveren.nl/')
await this.page await this.page
?.waitForSelector('input[name=username]') ?.waitForSelector('input[name=username]')
@ -52,6 +54,7 @@ export class Runner {
private async makeReservations(): Promise<Reservation[]> { private async makeReservations(): Promise<Reservation[]> {
for (let i = 0; i < this.reservations.length; i++) { for (let i = 0; i < this.reservations.length; i++) {
Logger.debug('Making reservation', this.reservations[i].format());
await this.makeReservation(this.reservations[i]) await this.makeReservation(this.reservations[i])
} }
@ -66,24 +69,42 @@ export class Runner {
await this.confirmReservation() await this.confirmReservation()
reservation.booked = true reservation.booked = true
} catch (err) { } catch (err) {
console.error(err) Logger.error('Error making reservation', reservation.format());
} }
} }
private getLastVisibleDay(): Dayjs {
const lastDayOfMonth = dayjs().add(1, 'month').set('date', 0);
let daysToAdd = 0;
switch (lastDayOfMonth.day()) {
case 0: daysToAdd = 0; break;
default: daysToAdd = 7 - lastDayOfMonth.day(); break;
}
return lastDayOfMonth.add(daysToAdd, 'day');
}
private async navigateToDay(date: Dayjs): Promise<void> { private async navigateToDay(date: Dayjs): Promise<void> {
Logger.debug(`Navigating to ${date.format()}`);
if (this.getLastVisibleDay().isBefore(date)) {
Logger.debug('Date is on different page, increase month');
await this.page?.waitForSelector('td.month.next').then((d) => d?.click());
}
await this.page await this.page
?.waitForSelector( ?.waitForSelector(
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get('day')}` `td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get('date')}`
) )
.then((d) => d?.click()) .then((d) => d?.click())
await this.page?.waitForSelector( await this.page?.waitForSelector(
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get( `td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
'day' 'date'
)}.selected` )}.selected`
) )
} }
private async selectAvailableTime(res: Reservation): Promise<void> { private async selectAvailableTime(res: Reservation): Promise<void> {
Logger.debug('Selecting available time', res.format());
let freeCourt: ElementHandle | null | undefined let freeCourt: ElementHandle | null | undefined
let i = 0 let i = 0
while (i < res.possibleDates.length && !freeCourt) { while (i < res.possibleDates.length && !freeCourt) {
@ -103,6 +124,7 @@ export class Runner {
} }
private async selectOpponent(opponent: Opponent): Promise<void> { private async selectOpponent(opponent: Opponent): Promise<void> {
Logger.debug('Selecting opponent', opponent);
const player2Search = await this.page?.waitForSelector( const player2Search = await this.page?.waitForSelector(
'tr.res-make-player-2 > td > input' 'tr.res-make-player-2 > td > input'
) )

View file

@ -1,9 +1,12 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { v4 } from 'uuid'
import { Logger, LogLevel } from './common/logger'
import { ReservationRequest } from './common/request' import { ReservationRequest } from './common/request'
import { Reservation } from './common/reservation' import { Reservation } from './common/reservation'
import { Runner } from './common/runner' import { Runner } from './common/runner'
const run = async (request: ReservationRequest) => { const run = async (request: ReservationRequest) => {
Logger.instantiate('local', v4(), LogLevel.DEBUG);
const { username, password, dateRange, opponent } = request const { username, password, dateRange, opponent } = request
const reservation = new Reservation(dateRange, opponent) const reservation = new Reservation(dateRange, opponent)

View file

@ -1,4 +1,4 @@
import { Worker } from "../types" import { Worker } from '../types'
export const work: Worker<undefined, void> = async (): Promise<void> => { export const work: Worker<undefined, void> = async (): Promise<void> => {
return return

View file

@ -12,9 +12,5 @@ export default {
format: 'cjs', format: 'cjs',
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [typescript({ module: 'esnext' }), nodeResolve(), commonjs()],
typescript({ module: "esnext" }),
nodeResolve(),
commonjs(),
]
} }

View file

@ -12,18 +12,17 @@ export interface ScheduledReservationRequest {
scheduledFor?: Dayjs scheduledFor?: Dayjs
} }
export interface ReservationSchedulerResult { export interface SchedulerResult {
scheduledReservationRequest?: ScheduledReservationRequest scheduledReservationRequest?: ScheduledReservationRequest
} }
export interface ReservationSchedulerInput export interface SchedulerInput extends Omit<ReservationRequest, 'dateRange'> {
extends Omit<ReservationRequest, 'dateRange'> {
dateRange: { start: string; end: string } dateRange: { start: string; end: string }
} }
export const work: Worker<ReservationSchedulerInput, ReservationSchedulerResult> = async ( export const work: Worker<SchedulerInput, SchedulerResult> = async (
payload: ReservationSchedulerInput, payload: SchedulerInput
): Promise<ReservationSchedulerResult> => { ): Promise<SchedulerResult> => {
Logger.instantiate('reservationScheduler', v4(), LogLevel.DEBUG) Logger.instantiate('reservationScheduler', v4(), LogLevel.DEBUG)
Logger.debug('Handling reservation', { payload }) Logger.debug('Handling reservation', { payload })
let reservationRequest: ReservationRequest let reservationRequest: ReservationRequest

View file

@ -12,9 +12,5 @@ export default {
format: 'cjs', format: 'cjs',
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [typescript({ module: 'esnext' }), nodeResolve(), commonjs()],
typescript({ module: "esnext" }),
nodeResolve(),
commonjs(),
]
} }

View file

@ -1,15 +1,15 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ValidationError, ValidationErrorCode } from '../../src/common/request' import { ValidationError, ValidationErrorCode } from '../../src/common/request'
import { work, ReservationSchedulerInput, ReservationSchedulerResult } from '../../src/workers/scheduler' import { work, SchedulerInput, SchedulerResult } from '../../src/workers/scheduler'
jest.mock('../../src/common/logger') jest.mock('../../src/common/logger')
describe('reservationScheduler', () => { describe('scheduler', () => {
test('should handle valid requests within reservation window', async () => { test('should handle valid requests within reservation window', async () => {
const start = dayjs().add(15, 'minutes') const start = dayjs().add(15, 'minutes')
const end = start.add(15, 'minutes') const end = start.add(15, 'minutes')
const payload: ReservationSchedulerInput = { const payload: SchedulerInput = {
username: "collin", username: "collin",
password: "password", password: "password",
dateRange: { start: start.toISOString(), end: end.toISOString() }, dateRange: { start: start.toISOString(), end: end.toISOString() },
@ -17,7 +17,7 @@ describe('reservationScheduler', () => {
} }
await expect(work(payload)).resolves await expect(work(payload)).resolves
.toMatchObject<ReservationSchedulerResult>({ .toMatchObject<SchedulerResult>({
scheduledReservationRequest: { scheduledReservationRequest: {
reservationRequest: { reservationRequest: {
username: 'collin', username: 'collin',
@ -31,14 +31,14 @@ describe('reservationScheduler', () => {
test('should handle valid requests outside of reservation window', async () => { test('should handle valid requests outside of reservation window', async () => {
const start = dayjs().add(15, 'days') const start = dayjs().add(15, 'days')
const end = start.add(15, 'minutes') const end = start.add(15, 'minutes')
const payload: ReservationSchedulerInput = { const payload: SchedulerInput = {
username: "collin", username: "collin",
password: "password", password: "password",
dateRange: { start: start.toISOString(), end: end.toISOString() }, dateRange: { start: start.toISOString(), end: end.toISOString() },
opponent: { id: "123", name: "collin" } opponent: { id: "123", name: "collin" }
} }
await expect(work(payload)).resolves.toMatchObject<ReservationSchedulerResult>({ await expect(work(payload)).resolves.toMatchObject<SchedulerResult>({
scheduledReservationRequest: { scheduledReservationRequest: {
reservationRequest: { reservationRequest: {
username: 'collin', username: 'collin',
@ -55,7 +55,7 @@ describe('reservationScheduler', () => {
const start = dayjs().add(15, 'days') const start = dayjs().add(15, 'days')
const end = start.add(15, 'minutes') const end = start.add(15, 'minutes')
const payload: ReservationSchedulerInput = { const payload: SchedulerInput = {
password: "password", password: "password",
dateRange: { start: start.toISOString(), end: end.toISOString() }, dateRange: { start: start.toISOString(), end: end.toISOString() },
opponent: { id: "123", name: "collin" } opponent: { id: "123", name: "collin" }