You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

188 lines
7.6 KiB
TypeScript

import express, { Router, Request, Response, NextFunction } from "express";
import Joi from "joi"
import dayjs from "dayjs"
import isoWeek from "dayjs/plugin/isoWeek"
import utc from "dayjs/plugin/utc"
import { client } from "../db";
import { DatabaseError } from "pg";
import Trainer from "../interfaces/trainer";
import { ReservedTimeslots, Timeslot, WeeklyTimeslot } from "../interfaces/timeslot";
import { idSchema, timeSchema } from "../schemas";
dayjs.extend(isoWeek);
dayjs.extend(utc);
const router: Router = express.Router();
const timeslotFiltersSchema = Joi.object({
trainer: Joi.array().single().items(
idSchema.required()
),
center: idSchema,
startDate: Joi.date().default(Date.now()),
endDate: Joi.date().default(Date.now()),
startTime: timeSchema,
endTime: timeSchema
});
interface TimeslotFilters {
trainer?: number[]
center?: number
startDate: string
endDate: string
startTime?: string
endTime?: string
}
interface DatabaseResult {
trainer: Trainer
timeslots: WeeklyTimeslot[]
reserved_timeslots: ReservedTimeslots[]
}
interface TrainerWithAvailableTimeslots extends Trainer {
timeslots: Timeslot[]
}
router.get("/timeslot", async (req: Request, res: Response) => {
try {
const validation = timeslotFiltersSchema.validate(req.query, { abortEarly: false, stripUnknown: true });
if (validation.error !== undefined) {
return res.status(400).send(validation.error.details);
}
const timeslotFilters: TimeslotFilters = validation.value;
let filterStartDate: dayjs.Dayjs = dayjs(timeslotFilters.startDate).utcOffset(0).startOf("date");
const filterEndDate: dayjs.Dayjs = dayjs(timeslotFilters.endDate).utcOffset(0).startOf("date");
const nowDate: dayjs.Dayjs = dayjs().utcOffset(0).startOf("date");
if (filterStartDate.isBefore(nowDate)) {
filterStartDate = nowDate;
}
let weekdays = [];
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")) {
weekdays.push(day.isoWeekday());
}
const queryResult = await client.query(`
SELECT
weekly_timeslots.trainer_id,
json_build_object(
'id',weekly_timeslots.trainer_id,
'first_name',users.first_name,
'last_name',users.last_name,
'center_id',trainers.center_id,
'center_name',centers.name
) as trainer,
json_agg(json_build_object(
'day_of_week',day_of_week,
'start_time',start_time,
'end_time',end_time)
) as timeslots,
(
SELECT json_agg(json_build_object(
'id', id,
'start_time', start_time,
'end_time', end_time
)) FROM public.reserved_timeslots
WHERE
(start_time between $1 AND $2
OR end_time between $1 AND $2)
AND weekly_timeslots.trainer_id = reserved_timeslots.trainer_id
) as reserved_timeslots
FROM
weekly_timeslots
JOIN trainers ON weekly_timeslots.trainer_id = trainers.id
JOIN users ON trainers.user_id = users.id
JOIN centers ON trainers.center_id = centers.id
WHERE
((trainer_id = ANY($3)) OR $4)
AND (day_of_week = ANY($5))
AND (trainers.center_id = $6 OR $7)
GROUP BY
weekly_timeslots.trainer_id,
users.first_name,
users.last_name,
trainers.center_id,
centers.name,
trainer_id;
`, [
new Date(timeslotFilters.startDate).toISOString(),
new Date(timeslotFilters.endDate).toISOString(),
timeslotFilters.trainer !== undefined ? timeslotFilters.trainer : [],
timeslotFilters.trainer === undefined,
weekdays,
timeslotFilters.center,
timeslotFilters.center === undefined
]);
const databaseResult: DatabaseResult[] = queryResult.rows;
const trainers: TrainerWithAvailableTimeslots[] = [];
for (const trainer of databaseResult) {
const trainerWithAvailableTimeslots: TrainerWithAvailableTimeslots = {
...trainer.trainer,
timeslots: []
}
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")) {
const weekDay = day.isoWeekday();
const reservedTimeslots = trainer.reserved_timeslots !== null ? trainer.reserved_timeslots : [];
timeslots: for (const timeslot of trainer.timeslots) {
if (timeslot.day_of_week !== weekDay)
continue timeslots;
if (timeslotFilters.startTime) {
if (parseInt(timeslotFilters.startTime.split(":")[0]) > parseInt(timeslot.start_time.split(":")[0]))
continue timeslots;
if (timeslotFilters.startTime.split(":")[0] === timeslot.start_time.split(":")[0] &&
parseInt(timeslotFilters.startTime.split(":")[1]) > parseInt(timeslot.start_time.split(":")[1]))
continue timeslots;
}
if (timeslotFilters.endTime) {
if (parseInt(timeslotFilters.endTime.split(":")[0]) < parseInt(timeslot.end_time.split(":")[0]))
continue timeslots;
if (timeslotFilters.endTime.split(":")[0] === timeslot.end_time.split(":")[0] &&
parseInt(timeslotFilters.endTime.split(":")[1]) < parseInt(timeslot.end_time.split(":")[1]))
continue timeslots;
}
const startTime = day.clone()
.hour(parseInt(timeslot.start_time.split(":")[0]))
.minute(parseInt(timeslot.start_time.split(":")[1]))
.second(parseInt(timeslot.start_time.split(":")[2]));
const endTime = day.clone()
.hour(parseInt(timeslot.end_time.split(":")[0]))
.minute(parseInt(timeslot.end_time.split(":")[1]))
.second(parseInt(timeslot.end_time.split(":")[2]));
for (const reservedTimeslot of reservedTimeslots) {
const reservedTimeStart = dayjs(reservedTimeslot.start_time);
const reservedTimeEnd = dayjs(reservedTimeslot.end_time);
if ((!reservedTimeStart.isBefore(startTime) && !reservedTimeStart.isAfter(endTime)) ||
(!reservedTimeEnd.isBefore(startTime) && !reservedTimeStart.isAfter(endTime))) {
continue timeslots;
}
}
trainerWithAvailableTimeslots.timeslots.push({
startDate: startTime.toDate(),
endDate: endTime.toDate()
});
}
}
trainers.push(trainerWithAvailableTimeslots);
}
return res.status(200).send(trainers);
} catch (error: DatabaseError | Error | any) {
console.error(error);
return res.sendStatus(500);
}
})
export default router;