Formatting fixes
This commit is contained in:
parent
a3a2f12082
commit
9da2d5e2f2
14 changed files with 359 additions and 340 deletions
|
|
@ -25,7 +25,7 @@ import { LoggerModule } from './logger/module'
|
|||
},
|
||||
defaultJobOptions: {
|
||||
removeOnComplete: true,
|
||||
}
|
||||
},
|
||||
}),
|
||||
ScheduleModule.forRoot(),
|
||||
ConfigModule.forRoot({ isGlobal: true }),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common'
|
||||
import { Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
export interface CustomResponse<T = unknown> {
|
||||
data: T
|
||||
data: T
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CustomResponseTransformInterceptor<T> implements NestInterceptor<T, CustomResponse<T>> {
|
||||
intercept(_context: ExecutionContext, next: CallHandler): Observable<CustomResponse<T>> {
|
||||
return next.handle().pipe(map(data => ({ data })))
|
||||
}
|
||||
export class CustomResponseTransformInterceptor<T>
|
||||
implements NestInterceptor<T, CustomResponse<T>>
|
||||
{
|
||||
intercept(
|
||||
_context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
): Observable<CustomResponse<T>> {
|
||||
return next.handle().pipe(map((data) => ({ data })))
|
||||
}
|
||||
}
|
||||
|
|
@ -12,27 +12,27 @@ dayjs.locale('nl')
|
|||
dayjs.tz.setDefault('Europe/Amsterdam')
|
||||
|
||||
export interface DateRange {
|
||||
start: dayjs.Dayjs
|
||||
end: dayjs.Dayjs
|
||||
start: dayjs.Dayjs
|
||||
end: dayjs.Dayjs
|
||||
}
|
||||
|
||||
export interface SerializedDateRange {
|
||||
start: string
|
||||
end: string
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
export const convertDateRangeStringToObject = ({
|
||||
start,
|
||||
end,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
start: string
|
||||
end: string
|
||||
start: string
|
||||
end: string
|
||||
}): DateRange => ({ start: dayjs(start), end: dayjs(end) })
|
||||
|
||||
const dayjsTz = (
|
||||
date?: string | number | Date | dayjs.Dayjs | null | undefined
|
||||
date?: string | number | Date | dayjs.Dayjs | null | undefined,
|
||||
) => {
|
||||
return dayjs(date).tz()
|
||||
return dayjs(date).tz()
|
||||
}
|
||||
|
||||
export default dayjsTz
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import { LoggerService } from './service'
|
|||
|
||||
@Injectable()
|
||||
export class LoggerMiddleware implements NestMiddleware {
|
||||
constructor(
|
||||
@Inject(LoggerService)
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
constructor(
|
||||
@Inject(LoggerService)
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
use(req: Request, _res: Response, next: NextFunction) {
|
||||
this.logger.log(`${req.method} ${req.originalUrl}`)
|
||||
next()
|
||||
}
|
||||
use(req: Request, _res: Response, next: NextFunction) {
|
||||
this.logger.log(`${req.method} ${req.originalUrl}`)
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export const RESERVATIONS_QUEUE_NAME = 'reservations'
|
||||
|
||||
export default () => ({
|
||||
queueName: RESERVATIONS_QUEUE_NAME
|
||||
queueName: RESERVATIONS_QUEUE_NAME,
|
||||
})
|
||||
|
|
@ -24,8 +24,12 @@ export class ReservationsCronService {
|
|||
timeZone: 'Europe/Amsterdam',
|
||||
})
|
||||
async handleDailyReservations() {
|
||||
const reservationsToPerform = await this.reservationService.getByDate()
|
||||
this.logger.log(`Found ${reservationsToPerform.length} reservations to perform`)
|
||||
await this.reservationsQueue.addBulk(reservationsToPerform.map((r) => ({ data: r })))
|
||||
}
|
||||
const reservationsToPerform = await this.reservationService.getByDate()
|
||||
this.logger.log(
|
||||
`Found ${reservationsToPerform.length} reservations to perform`,
|
||||
)
|
||||
await this.reservationsQueue.addBulk(
|
||||
reservationsToPerform.map((r) => ({ data: r })),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,33 +39,33 @@ export class Reservation {
|
|||
|
||||
@Exclude()
|
||||
public createPossibleDates(): Dayjs[] {
|
||||
const possibleDates: Dayjs[] = []
|
||||
const possibleDates: Dayjs[] = []
|
||||
|
||||
let possibleDate = dayjs(this.dateRangeStart).second(0).millisecond(0)
|
||||
while (possibleDate.isSameOrBefore(this.dateRangeEnd)) {
|
||||
possibleDates.push(possibleDate)
|
||||
possibleDate = possibleDate.add(15, 'minute')
|
||||
}
|
||||
let possibleDate = dayjs(this.dateRangeStart).second(0).millisecond(0)
|
||||
while (possibleDate.isSameOrBefore(this.dateRangeEnd)) {
|
||||
possibleDates.push(possibleDate)
|
||||
possibleDate = possibleDate.add(15, 'minute')
|
||||
}
|
||||
|
||||
return possibleDates
|
||||
}
|
||||
return possibleDates
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to check if a reservation is available for reservation in the system
|
||||
* @returns is reservation date within 7 days
|
||||
*/
|
||||
/**
|
||||
* Method to check if a reservation is available for reservation in the system
|
||||
* @returns is reservation date within 7 days
|
||||
*/
|
||||
@Exclude()
|
||||
public isAvailableForReservation(): boolean {
|
||||
public isAvailableForReservation(): boolean {
|
||||
return dayjs().diff(this.dateRangeStart, 'day') <= 7
|
||||
}
|
||||
}
|
||||
|
||||
@Exclude()
|
||||
public getAllowedReservationDate(): Dayjs {
|
||||
public getAllowedReservationDate(): Dayjs {
|
||||
return this.dateRangeStart
|
||||
.hour(0)
|
||||
.minute(0)
|
||||
.second(0)
|
||||
.millisecond(0)
|
||||
.subtract(7, 'days')
|
||||
}
|
||||
.hour(0)
|
||||
.minute(0)
|
||||
.second(0)
|
||||
.millisecond(0)
|
||||
.subtract(7, 'days')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { ReservationsWorker } from './worker'
|
|||
import { LoggerModule } from '../logger/module'
|
||||
import { RunnerModule } from '../runner/module'
|
||||
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
LoggerModule,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ export class ReservationsService {
|
|||
}
|
||||
|
||||
getByDate(date = dayjs()) {
|
||||
return this.reservationsRepository.createQueryBuilder()
|
||||
return this.reservationsRepository
|
||||
.createQueryBuilder()
|
||||
.where(`DATE(dateRangeStart, '-7 day') = DATE(:date)`, { date })
|
||||
.getMany()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,26 @@ import { LoggerService } from '../logger/service'
|
|||
|
||||
@Processor(RESERVATIONS_QUEUE_NAME)
|
||||
export class ReservationsWorker {
|
||||
constructor(
|
||||
@Inject(BaanReserverenService)
|
||||
private readonly brService: BaanReserverenService,
|
||||
constructor(
|
||||
@Inject(BaanReserverenService)
|
||||
private readonly brService: BaanReserverenService,
|
||||
|
||||
@Inject(LoggerService)
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
@Inject(LoggerService)
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
@Process()
|
||||
async handleReservationJob(job: Job<Reservation>) {
|
||||
const reservation = plainToInstance(Reservation, job.data, { groups: ['password'] })
|
||||
this.logger.log('Handling reservation', { reservation: instanceToPlain(reservation) })
|
||||
await this.performReservation(reservation)
|
||||
}
|
||||
@Process()
|
||||
async handleReservationJob(job: Job<Reservation>) {
|
||||
const reservation = plainToInstance(Reservation, job.data, {
|
||||
groups: ['password'],
|
||||
})
|
||||
this.logger.log('Handling reservation', {
|
||||
reservation: instanceToPlain(reservation),
|
||||
})
|
||||
await this.performReservation(reservation)
|
||||
}
|
||||
|
||||
async performReservation(reservation: Reservation) {
|
||||
await this.brService.performReservation(reservation)
|
||||
}
|
||||
async performReservation(reservation: Reservation) {
|
||||
await this.brService.performReservation(reservation)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,221 +9,221 @@ import { Reservation } from '../../reservations/entity'
|
|||
const baanReserverenRoot = 'https://squashcity.baanreserveren.nl'
|
||||
|
||||
export enum BaanReserverenUrls {
|
||||
Reservations = '/reservations',
|
||||
Logout = '/auth/logout',
|
||||
Reservations = '/reservations',
|
||||
Logout = '/auth/logout',
|
||||
}
|
||||
|
||||
enum SessionAction {
|
||||
NoAction,
|
||||
Logout,
|
||||
Login,
|
||||
NoAction,
|
||||
Logout,
|
||||
Login,
|
||||
}
|
||||
|
||||
interface BaanReserverenSession {
|
||||
username: string
|
||||
startedAt: Dayjs
|
||||
username: string
|
||||
startedAt: Dayjs
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BaanReserverenService {
|
||||
private session: BaanReserverenSession | null = null
|
||||
private session: BaanReserverenSession | null = null
|
||||
|
||||
constructor(
|
||||
@Inject(RunnerService)
|
||||
private readonly runnerService: RunnerService,
|
||||
constructor(
|
||||
@Inject(RunnerService)
|
||||
private readonly runnerService: RunnerService,
|
||||
|
||||
@Inject(EmptyPage)
|
||||
private readonly page: Page,
|
||||
) {}
|
||||
@Inject(EmptyPage)
|
||||
private readonly page: Page,
|
||||
) {}
|
||||
|
||||
private checkSession(username: string) {
|
||||
if (this.page.url().endsWith(BaanReserverenUrls.Reservations)) {
|
||||
return this.session?.username !== username
|
||||
? SessionAction.Logout
|
||||
: SessionAction.NoAction
|
||||
}
|
||||
return SessionAction.Login
|
||||
}
|
||||
private checkSession(username: string) {
|
||||
if (this.page.url().endsWith(BaanReserverenUrls.Reservations)) {
|
||||
return this.session?.username !== username
|
||||
? SessionAction.Logout
|
||||
: SessionAction.NoAction
|
||||
}
|
||||
return SessionAction.Login
|
||||
}
|
||||
|
||||
private startSession(username: string) {
|
||||
if (this.session && this.session.username !== username) {
|
||||
throw new Error('Session already started')
|
||||
}
|
||||
private startSession(username: string) {
|
||||
if (this.session && this.session.username !== username) {
|
||||
throw new Error('Session already started')
|
||||
}
|
||||
|
||||
if (this.session?.username === username) {
|
||||
return
|
||||
}
|
||||
if (this.session?.username === username) {
|
||||
return
|
||||
}
|
||||
|
||||
this.session = {
|
||||
username,
|
||||
startedAt: dayjs(),
|
||||
}
|
||||
}
|
||||
this.session = {
|
||||
username,
|
||||
startedAt: dayjs(),
|
||||
}
|
||||
}
|
||||
|
||||
private endSession() {
|
||||
this.session = null
|
||||
}
|
||||
private endSession() {
|
||||
this.session = null
|
||||
}
|
||||
|
||||
private async login(username: string, password: string) {
|
||||
await this.page
|
||||
.waitForSelector('input[name=username]')
|
||||
.then((i) => i?.type(username))
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginUsernameInputError(e)
|
||||
})
|
||||
await this.page
|
||||
.$('input[name=password]')
|
||||
.then((i) => i?.type(password))
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginPasswordInputError(e)
|
||||
})
|
||||
await this.page
|
||||
.$('button')
|
||||
.then((b) => b?.click())
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginSubmitError(e)
|
||||
})
|
||||
this.startSession(username)
|
||||
}
|
||||
private async login(username: string, password: string) {
|
||||
await this.page
|
||||
.waitForSelector('input[name=username]')
|
||||
.then((i) => i?.type(username))
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginUsernameInputError(e)
|
||||
})
|
||||
await this.page
|
||||
.$('input[name=password]')
|
||||
.then((i) => i?.type(password))
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginPasswordInputError(e)
|
||||
})
|
||||
await this.page
|
||||
.$('button')
|
||||
.then((b) => b?.click())
|
||||
.catch((e: Error) => {
|
||||
// throw new RunnerLoginSubmitError(e)
|
||||
})
|
||||
this.startSession(username)
|
||||
}
|
||||
|
||||
private async logout() {
|
||||
await this.page.goto(`${baanReserverenRoot}${BaanReserverenUrls.Logout}`)
|
||||
this.endSession()
|
||||
}
|
||||
private async logout() {
|
||||
await this.page.goto(`${baanReserverenRoot}${BaanReserverenUrls.Logout}`)
|
||||
this.endSession()
|
||||
}
|
||||
|
||||
private async init(reservation: Reservation) {
|
||||
await this.page.goto(baanReserverenRoot)
|
||||
const action = await this.checkSession(reservation.username)
|
||||
switch (action) {
|
||||
case SessionAction.Logout:
|
||||
await this.logout()
|
||||
await this.login(reservation.username, reservation.password)
|
||||
break
|
||||
case SessionAction.Login:
|
||||
await this.login(reservation.username, reservation.password)
|
||||
break
|
||||
case SessionAction.NoAction:
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
private async init(reservation: Reservation) {
|
||||
await this.page.goto(baanReserverenRoot)
|
||||
const action = await this.checkSession(reservation.username)
|
||||
switch (action) {
|
||||
case SessionAction.Logout:
|
||||
await this.logout()
|
||||
await this.login(reservation.username, reservation.password)
|
||||
break
|
||||
case SessionAction.Login:
|
||||
await this.login(reservation.username, reservation.password)
|
||||
break
|
||||
case SessionAction.NoAction:
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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 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> {
|
||||
if (this.getLastVisibleDay().isBefore(date)) {
|
||||
await this.page
|
||||
?.waitForSelector('td.month.next')
|
||||
.then((d) => d?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationMonthError(e)
|
||||
})
|
||||
}
|
||||
private async navigateToDay(date: Dayjs): Promise<void> {
|
||||
if (this.getLastVisibleDay().isBefore(date)) {
|
||||
await this.page
|
||||
?.waitForSelector('td.month.next')
|
||||
.then((d) => d?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationMonthError(e)
|
||||
})
|
||||
}
|
||||
|
||||
await this.page
|
||||
?.waitForSelector(
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
||||
'date'
|
||||
)}`
|
||||
)
|
||||
.then((d) => d?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationDayError(e)
|
||||
})
|
||||
await this.page
|
||||
?.waitForSelector(
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
||||
'date'
|
||||
)}.selected`
|
||||
)
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationSelectionError(e)
|
||||
})
|
||||
}
|
||||
await this.page
|
||||
?.waitForSelector(
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
||||
'date',
|
||||
)}`,
|
||||
)
|
||||
.then((d) => d?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationDayError(e)
|
||||
})
|
||||
await this.page
|
||||
?.waitForSelector(
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
||||
'date',
|
||||
)}.selected`,
|
||||
)
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerNavigationSelectionError(e)
|
||||
})
|
||||
}
|
||||
|
||||
private async selectAvailableTime(reservation: Reservation): Promise<void> {
|
||||
let freeCourt: ElementHandle | null | undefined
|
||||
let i = 0
|
||||
const possibleDates = reservation.createPossibleDates()
|
||||
while (i < possibleDates.length && !freeCourt) {
|
||||
const possibleDate = possibleDates[i]
|
||||
const timeString = possibleDate.format('HH:mm')
|
||||
const selector =
|
||||
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
||||
freeCourt = await this.page?.$(selector)
|
||||
i++
|
||||
}
|
||||
private async selectAvailableTime(reservation: Reservation): Promise<void> {
|
||||
let freeCourt: ElementHandle | null | undefined
|
||||
let i = 0
|
||||
const possibleDates = reservation.createPossibleDates()
|
||||
while (i < possibleDates.length && !freeCourt) {
|
||||
const possibleDate = possibleDates[i]
|
||||
const timeString = possibleDate.format('HH:mm')
|
||||
const selector =
|
||||
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
||||
freeCourt = await this.page?.$(selector)
|
||||
i++
|
||||
}
|
||||
|
||||
if (!freeCourt) {
|
||||
throw new NoCourtAvailableError()
|
||||
}
|
||||
if (!freeCourt) {
|
||||
throw new NoCourtAvailableError()
|
||||
}
|
||||
|
||||
await freeCourt.click().catch((e: Error) => {
|
||||
throw new RunnerCourtSelectionError(e)
|
||||
})
|
||||
}
|
||||
await freeCourt.click().catch((e: Error) => {
|
||||
throw new RunnerCourtSelectionError(e)
|
||||
})
|
||||
}
|
||||
|
||||
private async selectOpponent(id: string, name: string): Promise<void> {
|
||||
const player2Search = await this.page
|
||||
?.waitForSelector('tr.res-make-player-2 > td > input')
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchError(e)
|
||||
})
|
||||
await player2Search?.type(name).catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchInputError(e)
|
||||
})
|
||||
await this.page?.waitForNetworkIdle().catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchNetworkError(e)
|
||||
})
|
||||
await this.page
|
||||
?.$('select.br-user-select[name="players[2]"]')
|
||||
.then((d) => d?.select(id))
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchSelectionError(e)
|
||||
})
|
||||
}
|
||||
private async selectOpponent(id: string, name: string): Promise<void> {
|
||||
const player2Search = await this.page
|
||||
?.waitForSelector('tr.res-make-player-2 > td > input')
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchError(e)
|
||||
})
|
||||
await player2Search?.type(name).catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchInputError(e)
|
||||
})
|
||||
await this.page?.waitForNetworkIdle().catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchNetworkError(e)
|
||||
})
|
||||
await this.page
|
||||
?.$('select.br-user-select[name="players[2]"]')
|
||||
.then((d) => d?.select(id))
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerOpponentSearchSelectionError(e)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
?.waitForSelector('input#__make_submit2')
|
||||
.then((b) => b?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerReservationConfirmSubmitError(e)
|
||||
})
|
||||
}
|
||||
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
|
||||
?.waitForSelector('input#__make_submit2')
|
||||
.then((b) => b?.click())
|
||||
.catch((e: Error) => {
|
||||
throw new RunnerReservationConfirmSubmitError(e)
|
||||
})
|
||||
}
|
||||
|
||||
public async performReservation(reservation: Reservation) {
|
||||
await this.init(reservation)
|
||||
await this.navigateToDay(reservation.dateRangeStart)
|
||||
await this.selectAvailableTime(reservation)
|
||||
await this.selectOpponent(reservation.opponentId, reservation.opponentName)
|
||||
// await this.confirmReservation()
|
||||
}
|
||||
public async performReservation(reservation: Reservation) {
|
||||
await this.init(reservation)
|
||||
await this.navigateToDay(reservation.dateRangeStart)
|
||||
await this.selectAvailableTime(reservation)
|
||||
await this.selectOpponent(reservation.opponentId, reservation.opponentName)
|
||||
// await this.confirmReservation()
|
||||
}
|
||||
}
|
||||
|
||||
export class RunnerError extends Error {
|
||||
constructor(error: Error) {
|
||||
super(error.message)
|
||||
this.stack = error.stack
|
||||
}
|
||||
constructor(error: Error) {
|
||||
super(error.message)
|
||||
this.stack = error.stack
|
||||
}
|
||||
}
|
||||
export class PuppeteerError extends RunnerError {}
|
||||
export class PuppeteerBrowserLaunchError extends PuppeteerError {}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ import { BaanReserverenService } from './baanreserveren/service'
|
|||
import { LoggerModule } from '../logger/module'
|
||||
|
||||
@Module({
|
||||
providers: [RunnerService, BaanReserverenService, EmptyPageFactory],
|
||||
imports: [LoggerModule, BullModule.registerQueue({ name: 'reservations' })],
|
||||
exports: [EmptyPageFactory, BaanReserverenService],
|
||||
providers: [RunnerService, BaanReserverenService, EmptyPageFactory],
|
||||
imports: [LoggerModule, BullModule.registerQueue({ name: 'reservations' })],
|
||||
exports: [EmptyPageFactory, BaanReserverenService],
|
||||
})
|
||||
|
||||
export class RunnerModule {}
|
||||
|
|
@ -5,13 +5,13 @@ import { Page } from 'puppeteer'
|
|||
export const EmptyPage = Symbol.for('EmptyPage')
|
||||
|
||||
export const EmptyPageFactory: FactoryProvider<Page> = {
|
||||
provide: EmptyPage,
|
||||
useFactory: async (runnerService: RunnerService) => {
|
||||
const browser = await runnerService.getBrowser()
|
||||
const page = await browser.newPage()
|
||||
provide: EmptyPage,
|
||||
useFactory: async (runnerService: RunnerService) => {
|
||||
const browser = await runnerService.getBrowser()
|
||||
const page = await browser.newPage()
|
||||
|
||||
return page
|
||||
},
|
||||
inject: [RunnerService],
|
||||
scope: Scope.TRANSIENT,
|
||||
return page
|
||||
},
|
||||
inject: [RunnerService],
|
||||
scope: Scope.TRANSIENT,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +1,92 @@
|
|||
import { Inject, Injectable, BeforeApplicationShutdown, OnModuleInit, Scope } from '@nestjs/common'
|
||||
import puppeteer, { Browser, BrowserConnectOptions, BrowserLaunchArgumentOptions, LaunchOptions, Page } from 'puppeteer'
|
||||
import { LoggerService } from '../logger/service'
|
||||
|
||||
enum SessionAction {
|
||||
NoAction,
|
||||
Logout,
|
||||
Login,
|
||||
}
|
||||
import {
|
||||
Injectable,
|
||||
BeforeApplicationShutdown,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common'
|
||||
import puppeteer, {
|
||||
Browser,
|
||||
BrowserConnectOptions,
|
||||
BrowserLaunchArgumentOptions,
|
||||
LaunchOptions,
|
||||
} from 'puppeteer'
|
||||
|
||||
interface RunnerSession {
|
||||
username: string
|
||||
loggedInAt: Date
|
||||
username: string
|
||||
loggedInAt: Date
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RunnerService implements OnModuleInit, BeforeApplicationShutdown {
|
||||
private browser?: Browser
|
||||
private options: LaunchOptions &
|
||||
BrowserLaunchArgumentOptions &
|
||||
BrowserConnectOptions = {
|
||||
args: ['--disable-setuid-sandbox', '--no-sandbox'],
|
||||
headless: 'new'
|
||||
}
|
||||
private session: RunnerSession | null = null
|
||||
private browser?: Browser
|
||||
private options: LaunchOptions &
|
||||
BrowserLaunchArgumentOptions &
|
||||
BrowserConnectOptions = {
|
||||
args: ['--disable-setuid-sandbox', '--no-sandbox'],
|
||||
headless: 'new',
|
||||
}
|
||||
private session: RunnerSession | null = null
|
||||
|
||||
private async init() {
|
||||
try {
|
||||
if (!this.browser) {
|
||||
this.browser = await puppeteer.launch(this.options)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new PuppeteerBrowserLaunchError(error)
|
||||
}
|
||||
}
|
||||
private async init() {
|
||||
try {
|
||||
if (!this.browser) {
|
||||
this.browser = await puppeteer.launch(this.options)
|
||||
}
|
||||
} catch (error) {
|
||||
throw new PuppeteerBrowserLaunchError(error)
|
||||
}
|
||||
}
|
||||
|
||||
public async onModuleInit() {
|
||||
await this.init()
|
||||
}
|
||||
public async onModuleInit() {
|
||||
await this.init()
|
||||
}
|
||||
|
||||
public async beforeApplicationShutdown() {
|
||||
try {
|
||||
if (this.browser && this.browser.isConnected()) {
|
||||
await this.browser.close()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error shutting down browser', error)
|
||||
}
|
||||
}
|
||||
public async beforeApplicationShutdown() {
|
||||
try {
|
||||
if (this.browser && this.browser.isConnected()) {
|
||||
await this.browser.close()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('error shutting down browser', error)
|
||||
}
|
||||
}
|
||||
|
||||
public async getBrowser(): Promise<Browser> {
|
||||
await this.init()
|
||||
if (!this.browser) {
|
||||
throw new Error('Browser not initialized')
|
||||
}
|
||||
return this.browser
|
||||
}
|
||||
public async getBrowser(): Promise<Browser> {
|
||||
await this.init()
|
||||
if (!this.browser) {
|
||||
throw new Error('Browser not initialized')
|
||||
}
|
||||
return this.browser
|
||||
}
|
||||
|
||||
public async getSession(): Promise<RunnerSession | null> {
|
||||
return this.session
|
||||
}
|
||||
public async getSession(): Promise<RunnerSession | null> {
|
||||
return this.session
|
||||
}
|
||||
|
||||
public startSession(username: string) {
|
||||
if (this.session && this.session.username !== username) {
|
||||
throw new RunnerNewSessionError(new Error('Session already started'))
|
||||
}
|
||||
public startSession(username: string) {
|
||||
if (this.session && this.session.username !== username) {
|
||||
throw new RunnerNewSessionError(new Error('Session already started'))
|
||||
}
|
||||
|
||||
if (this.session?.username === username) {
|
||||
return
|
||||
}
|
||||
if (this.session?.username === username) {
|
||||
return
|
||||
}
|
||||
|
||||
this.session = {
|
||||
username,
|
||||
loggedInAt: new Date(),
|
||||
}
|
||||
}
|
||||
this.session = {
|
||||
username,
|
||||
loggedInAt: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
public endSession() {
|
||||
this.session = null
|
||||
}
|
||||
public endSession() {
|
||||
this.session = null
|
||||
}
|
||||
}
|
||||
|
||||
export class RunnerError extends Error {
|
||||
constructor(error: Error) {
|
||||
super(error.message)
|
||||
this.stack = error.stack
|
||||
}
|
||||
constructor(error: Error) {
|
||||
super(error.message)
|
||||
this.stack = error.stack
|
||||
}
|
||||
}
|
||||
export class PuppeteerError extends RunnerError {}
|
||||
export class PuppeteerBrowserLaunchError extends PuppeteerError {}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue