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
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; |