Adding ability to reserve for others and to filter out shitty courts
This commit is contained in:
parent
54aef1ece8
commit
a7c955a18d
4 changed files with 179 additions and 24 deletions
|
|
@ -0,0 +1,93 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveUsernameAndPasswordFromReservations1696583071967
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'RemoveUsernameAndPasswordFromReservations1696583071967'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_reservations"("id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId") SELECT "id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId" FROM "reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "temporary_reservations" RENAME TO "reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_recurring_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dayOfWeek" integer NOT NULL, "timeStart" varchar(6) NOT NULL, "timeEnd" varchar(6) NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_recurring_reservations"("id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName") SELECT "id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName" FROM "recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "recurring_reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "temporary_recurring_reservations" RENAME TO "recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer, "ownerId" varchar(32) NOT NULL)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_reservations"("id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId", "ownerId") SELECT "id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId", "unknown" FROM "reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "temporary_reservations" RENAME TO "reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_recurring_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dayOfWeek" integer NOT NULL, "timeStart" varchar(6) NOT NULL, "timeEnd" varchar(6) NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL, "ownerId" varchar(32) NOT NULL)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_recurring_reservations"("id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName", "ownerId") SELECT "id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName", "unknown" FROM "recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "recurring_reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "temporary_recurring_reservations" RENAME TO "recurring_reservations"`,
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "recurring_reservations" RENAME TO "temporary_recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "recurring_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dayOfWeek" integer NOT NULL, "timeStart" varchar(6) NOT NULL, "timeEnd" varchar(6) NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "recurring_reservations"("id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName") SELECT "id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName" FROM "temporary_recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "temporary_recurring_reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reservations" RENAME TO "temporary_reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "reservations"("id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId") SELECT "id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId" FROM "temporary_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "temporary_reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "recurring_reservations" RENAME TO "temporary_recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "recurring_reservations" ("id" varchar PRIMARY KEY NOT NULL, "username" varchar(64) NOT NULL, "password" varchar(255) NOT NULL, "dayOfWeek" integer NOT NULL, "timeStart" varchar(6) NOT NULL, "timeEnd" varchar(6) NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "recurring_reservations"("id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName") SELECT "id", "dayOfWeek", "timeStart", "timeEnd", "opponentId", "opponentName" FROM "temporary_recurring_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "temporary_recurring_reservations"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "reservations" RENAME TO "temporary_reservations"`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "reservations" ("id" varchar PRIMARY KEY NOT NULL, "username" varchar(64) NOT NULL, "password" varchar(255) NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "opponentId" varchar(32) NOT NULL, "opponentName" varchar(255) NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer)`,
|
||||
)
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "reservations"("id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId") SELECT "id", "dateRangeStart", "dateRangeEnd", "opponentId", "opponentName", "waitListed", "waitingListId" FROM "temporary_reservations"`,
|
||||
)
|
||||
await queryRunner.query(`DROP TABLE "temporary_reservations"`)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,8 @@ export class RecurringReservation {
|
|||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column('varchar', { length: 64, nullable: false })
|
||||
username: string
|
||||
|
||||
@Transform(({ value, options: { groups = [] } }) =>
|
||||
groups.includes('password') ? value : '***',
|
||||
)
|
||||
@Column('varchar', { length: 255, nullable: false })
|
||||
password: string
|
||||
@Column('varchar', { length: 32, nullable: false })
|
||||
ownerId: string
|
||||
|
||||
@Column('int', { nullable: false })
|
||||
@IsEnum(DayOfWeek)
|
||||
|
|
@ -60,8 +54,6 @@ export class RecurringReservation {
|
|||
.set('minute', Number.parseInt(minuteEnd))
|
||||
.add(daysInAdvance, 'days')
|
||||
const reservation = new Reservation({
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
dateRangeStart: dateRangeStart,
|
||||
dateRangeEnd: dateRangeEnd,
|
||||
opponentId: this.opponentId,
|
||||
|
|
|
|||
|
|
@ -10,14 +10,8 @@ export class Reservation {
|
|||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column('varchar', { length: 64, nullable: false })
|
||||
username: string
|
||||
|
||||
@Transform(({ value, options: { groups = [] } }) =>
|
||||
groups.includes('password') ? value : '***',
|
||||
)
|
||||
@Column('varchar', { length: 255, nullable: false })
|
||||
password: string
|
||||
@Column('varchar', { length: 32, nullable: false })
|
||||
ownerId: string
|
||||
|
||||
@Column('datetime', {
|
||||
nullable: false,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Inject, Injectable } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { instanceToPlain } from 'class-transformer'
|
||||
import { Dayjs } from 'dayjs'
|
||||
import { ElementHandle, Page } from 'puppeteer'
|
||||
|
|
@ -28,20 +29,49 @@ interface BaanReserverenSession {
|
|||
startedAt: Dayjs
|
||||
}
|
||||
|
||||
// TODO: Add to DB to make configurable
|
||||
enum Court {
|
||||
One = '51',
|
||||
Two = '52',
|
||||
Three = '53',
|
||||
Four = '54',
|
||||
Five = '55',
|
||||
Six = '56',
|
||||
Seven = '57',
|
||||
Eight = '58',
|
||||
Nine = '59',
|
||||
Ten = '60',
|
||||
Eleven = '61',
|
||||
Twelve = '62',
|
||||
Thirteen = '63',
|
||||
}
|
||||
|
||||
const MIN_TYPING_DELAY_MS = 10
|
||||
const MAX_TYPING_DELAY_MS = 30
|
||||
|
||||
@Injectable()
|
||||
export class BaanReserverenService {
|
||||
private session: BaanReserverenSession | null = null
|
||||
private username: string
|
||||
private password: string
|
||||
|
||||
constructor(
|
||||
@Inject(LoggerService)
|
||||
private readonly loggerService: LoggerService,
|
||||
|
||||
@Inject(ConfigService)
|
||||
private readonly configService: ConfigService,
|
||||
|
||||
@Inject(EmptyPage)
|
||||
private readonly page: Page,
|
||||
) {}
|
||||
) {
|
||||
this.username = this.configService.getOrThrow<string>(
|
||||
'BAANRESERVEREN_USERNAME',
|
||||
)
|
||||
this.password = this.configService.getOrThrow<string>(
|
||||
'BAANRESERVEREN_PASSWORD',
|
||||
)
|
||||
}
|
||||
|
||||
// tryna be sneaky
|
||||
private getTypingDelay() {
|
||||
|
|
@ -63,7 +93,7 @@ export class BaanReserverenService {
|
|||
return SessionAction.Login
|
||||
}
|
||||
|
||||
return this.session?.username !== username
|
||||
return this.session?.username !== this.username
|
||||
? SessionAction.Logout
|
||||
: SessionAction.NoAction
|
||||
}
|
||||
|
|
@ -130,14 +160,14 @@ export class BaanReserverenService {
|
|||
})
|
||||
await this.page.goto(BAAN_RESERVEREN_ROOT_URL)
|
||||
await this.page.waitForNetworkIdle()
|
||||
const action = await this.checkSession(reservation.username)
|
||||
const action = await this.checkSession(this.username)
|
||||
switch (action) {
|
||||
case SessionAction.Logout:
|
||||
await this.logout()
|
||||
await this.login(reservation.username, reservation.password)
|
||||
await this.login(this.username, this.password)
|
||||
break
|
||||
case SessionAction.Login:
|
||||
await this.login(reservation.username, reservation.password)
|
||||
await this.login(this.username, this.password)
|
||||
break
|
||||
case SessionAction.NoAction:
|
||||
default:
|
||||
|
|
@ -278,6 +308,22 @@ export class BaanReserverenService {
|
|||
await this.page.waitForNetworkIdle()
|
||||
}
|
||||
|
||||
private async filterShittyCourts(
|
||||
freeCourts: ElementHandle<Element>[],
|
||||
): Promise<ElementHandle | null> {
|
||||
for (const fc of freeCourts) {
|
||||
const fcValue = await fc.jsonValue()
|
||||
switch (fcValue.slot) {
|
||||
case Court.Five:
|
||||
case Court.Six:
|
||||
continue
|
||||
default:
|
||||
return fc
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private async selectAvailableTime(reservation: Reservation) {
|
||||
this.loggerService.debug('Selecting available time', {
|
||||
reservation: instanceToPlain(reservation),
|
||||
|
|
@ -291,7 +337,12 @@ export class BaanReserverenService {
|
|||
const timeString = possibleDate.format('HH:mm')
|
||||
const selector =
|
||||
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
||||
freeCourt = await this.page.$(selector)
|
||||
const freeCourts = await this.page.$$(selector)
|
||||
freeCourt = await this.filterShittyCourts(freeCourts)
|
||||
if (freeCourts.length > 0 && freeCourt == null) {
|
||||
// If there is only a shitty court, sucks
|
||||
freeCourt = freeCourts[0]
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
|
|
@ -306,6 +357,19 @@ export class BaanReserverenService {
|
|||
})
|
||||
}
|
||||
|
||||
private async selectOwner(id: string) {
|
||||
this.loggerService.debug('Selecting owner', { id })
|
||||
await this.page.waitForNetworkIdle().catch((e: Error) => {
|
||||
throw new RunnerOwnerSearchNetworkError(e)
|
||||
})
|
||||
await this.page
|
||||
.$('select.br-user-select[name="players[1]"]')
|
||||
.then((d) => d?.select(id))
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerOwnerSearchSelectionError(e)
|
||||
})
|
||||
}
|
||||
|
||||
private async selectOpponent(id: string, name: string) {
|
||||
this.loggerService.debug('Selecting opponent', { id, name })
|
||||
const player2Search = await this.page
|
||||
|
|
@ -403,6 +467,7 @@ export class BaanReserverenService {
|
|||
await this.init(reservation)
|
||||
await this.navigateToDay(reservation.dateRangeStart)
|
||||
await this.selectAvailableTime(reservation)
|
||||
await this.selectOwner(reservation.ownerId)
|
||||
await this.selectOpponent(reservation.opponentId, reservation.opponentName)
|
||||
await this.confirmReservation()
|
||||
}
|
||||
|
|
@ -552,6 +617,17 @@ export class NoCourtAvailableError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class RunnerOwnerSearchNetworkError extends RunnerError {
|
||||
constructor(error: Error) {
|
||||
super(error, 'RunnerOwnerSearchNetworkError')
|
||||
}
|
||||
}
|
||||
export class RunnerOwnerSearchSelectionError extends RunnerError {
|
||||
constructor(error: Error) {
|
||||
super(error, 'RunnerOwnerSearchSelectionError')
|
||||
}
|
||||
}
|
||||
|
||||
export class RunnerOpponentSearchError extends RunnerError {
|
||||
constructor(error: Error) {
|
||||
super(error, 'RunnerOpponentSearchError')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue