Added /timeslot endpoint
continuous-integration/drone/push Build is failing Details

main
Filip Borum Poulsen 3 years ago
parent c746675cf7
commit 24dff498f8

@ -13,12 +13,14 @@
| [ ] | PUT | /trainer | Trainer can change profile information | Trainer |
| [x] | PUT | /trainer/:id | Admin can change trainer information | Admin |
| [ ] | DELETE | /trainer/:id | Admin can delete trainer | Admin |
| [ ] | GET | /trainer/:id/time_slot | Get available timeslots for specific trainer | |
| [ ] | GET | /trainer/time_slot | Trainer can get reserved timeslots | Trainer |
| [ ] | PUT | /trainer/time_slot/:id | Trainer can change reserved timeslot | Trainer |
| [ ] | DELTE | /trainer/time_slot/:id | Trainer can delete reserved timeslot | Trainer |
| [x] | GET | /timeslot | Filter for available timeslots | |
| [ ] | GET | /trainer/timeslot | Trainer can get reserved timeslots | Trainer |
| [ ] | PUT | /trainer/timeslot/:id | Trainer can change reserved timeslot | Trainer |
| [ ] | DELTE | /trainer/timeslot/:id | Trainer can delete reserved timeslot | Trainer |
| [ ] | GET | /order | User can get list of orders | User |
| [ ] | POST | /order | User can request an order | User |
| [ ] | GET | /order/:id | User can get order details | User |
| [ ] | POST | /order/:id/confirm | User can confirm the order | User |
| [ ] | PUT | /order/:id | User can move order | User |
| [ ] | DELETE | /order/:id | User can cancel order | User |
| [ ] | GET | /user | User can get profile information | User |

@ -11,6 +11,7 @@
"dependencies": {
"bcrypt": "^5.1.0",
"cookie-parser": "^1.4.6",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"joi": "^17.9.1",
@ -732,6 +733,11 @@
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dayjs": {
"version": "1.11.7",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
"integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",

@ -7,7 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node dist/index.js",
"dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/index.js\"",
"dev": "concurrently \"tsc --watch\" \"nodemon -q dist/index.js\"",
"migrate": "ts-node src/migrations/index.ts",
"populate": "ts-node src/migrations/populate.ts"
},
@ -16,6 +16,7 @@
"dependencies": {
"bcrypt": "^5.1.0",
"cookie-parser": "^1.4.6",
"dayjs": "^1.11.7",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"joi": "^17.9.1",

@ -0,0 +1,18 @@
import Trainer from "./trainer"
export interface Timeslot {
startTime: Date
endTime: Date
}
export interface WeeklyTimeslot {
day_of_week: number
start_time: string
end_time: string
}
export interface ReservedTimeslots {
id: number
startTime: string | Date
endTime: string | Date
}

@ -0,0 +1,15 @@
import { Request, Response, NextFunction } from "express";
import { client } from "../db";
export async function trainerExists(req: Request, res: Response, next: NextFunction) {
const trainerExistsResult = await client.query(`
SELECT 1 FROM trainers where id = $1;
`, [
req.params.trainer_id
]);
if (trainerExistsResult.rowCount < 1) {
return res.sendStatus(404);
}
return next();
}

@ -48,12 +48,12 @@ RETURNING id, user_id;
const weekly_timeslots = await client.query(`
INSERT INTO weekly_timeslots (trainer_id, day_of_week, start_time, end_time) VALUES
($1, 0, '11:00:00', '12:00:00'),
($1, 1, '10:00:00', '13:00:00'),
($1, 2, '10:00:00', '15:00:00'),
($2, 0, '10:00:00', '11:00:00'),
($2, 0, '11:00:00', '12:00:00'),
($2, 0, '12:00:00', '13:00:00')
($1, 1, '11:00:00', '12:00:00'),
($1, 2, '10:00:00', '13:00:00'),
($1, 3, '10:00:00', '15:00:00'),
($2, 1, '10:00:00', '11:00:00'),
($2, 1, '11:00:00', '12:00:00'),
($2, 1, '12:00:00', '13:00:00')
RETURNING id;
`, [
trainers.rows[0].id,

@ -6,10 +6,12 @@ import login from "./login"
import register from "./register"
import center from "./center";
import trainer from "./trainer";
import timeslot from "./timeslot";
router.use(login);
router.use(register);
router.use(center);
router.use(trainer);
router.use(timeslot);
export default router;

@ -0,0 +1,167 @@
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, TrainerWeeklyTimeslot, WeeklyTimeslot } from "../interfaces/timeslot";
import { TrainerAuth } from "../middlewares/auth";
import { trainerExists } from "../middlewares/trainer";
import { idSchema } from "../schemas";
dayjs.extend(isoWeek)
dayjs.extend(utc)
const router: Router = express.Router();
const timeSchema = Joi.string().regex(/^\d{1,2}:\d{1,2}$/);
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: Date
endDate: Date
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;
const filterStartDate: dayjs.Dayjs = dayjs(timeslotFilters.startDate).utcOffset(0).startOf("date");
const filterEndDate: dayjs.Dayjs = dayjs(timeslotFilters.endDate).utcOffset(0).startOf("date");
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 '2023-04-16' AND '2023-04-20'
OR end_time between '2023-04-16' AND '2023-04-20')
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($1)) OR $2)
AND (day_of_week = ANY($3))
GROUP BY
weekly_timeslots.trainer_id,
users.first_name,
users.last_name,
trainers.center_id,
centers.name,
trainer_id;
`, [
timeslotFilters.trainer !== undefined ? timeslotFilters.trainer : [1],
timeslotFilters.trainer === undefined,
weekdays
]);
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;
timeslots: for (const timeslot of trainer.timeslots) {
if (timeslot.day_of_week !== weekDay)
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.startTime);
const reservedTimeEnd = dayjs(reservedTimeslot.endTime);
if ((!reservedTimeStart.isBefore(startTime) && !reservedTimeStart.isAfter(endTime)) ||
(!reservedTimeEnd.isBefore(startTime) && !reservedTimeStart.isAfter(endTime))) {
continue timeslots;
}
}
trainerWithAvailableTimeslots.timeslots.push({
startTime: startTime.toDate(),
endTime: 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;

@ -4,7 +4,8 @@ import Joi from "joi"
import { client } from "../db";
import { DatabaseError } from "pg";
import Trainer from "../interfaces/trainer";
import { AdminAuth, UserAuth } from "../middlewares/auth";
import { AdminAuth } from "../middlewares/auth";
import { trainerExists } from "../middlewares/trainer";
const router: Router = express.Router();
@ -85,18 +86,8 @@ const updateTrainerSchema = Joi.object({
hourly_price: Joi.number().integer().positive().required()
});
router.put("/trainer/:id", AdminAuth, async (req: Request, res: Response) => {
router.put("/trainer/:trainer_id", AdminAuth, trainerExists, async (req: Request, res: Response) => {
try {
const lookupResult = await client.query(`
SELECT 1 FROM trainers where id = $1;
`, [
req.params.id
]);
if (lookupResult.rowCount < 1) {
return res.sendStatus(404);
}
const validation = updateTrainerSchema.validate(req.body, { abortEarly: false });
if (validation.error !== undefined) {
return res.status(400).send(validation.error.details);

@ -0,0 +1,3 @@
import Joi from "joi"
export const idSchema = Joi.number().integer().positive();
Loading…
Cancel
Save