Updating runner to change to next month if needed; Added some more logging; Cleaning up some scripts
This commit is contained in:
parent
767a5930a6
commit
98bd540130
12 changed files with 76 additions and 43 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export type Worker<I = unknown, O = unknown> = (payload: I) => O | Promise<O>
|
export type Worker<I = unknown, O = unknown> = (payload: I) => O | Promise<O>
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue