From e841a9e2fe03ea44e2c448da35bf8370a87f0e89 Mon Sep 17 00:00:00 2001 From: Filip Borum Poulsen Date: Tue, 18 Apr 2023 10:04:31 +0200 Subject: [PATCH] Added create order endpoint --- server/src/db.ts | 4 +- server/src/interfaces/auth.ts | 2 +- server/src/routes/index.ts | 2 + server/src/routes/order.ts | 156 ++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 server/src/routes/order.ts diff --git a/server/src/db.ts b/server/src/db.ts index 8a127ac..4f0b0fe 100644 --- a/server/src/db.ts +++ b/server/src/db.ts @@ -1,3 +1,3 @@ -import { Client } from 'pg'; -export const client = new Client(); +import { Pool } from 'pg'; +export const client = new Pool(); client.connect(); \ No newline at end of file diff --git a/server/src/interfaces/auth.ts b/server/src/interfaces/auth.ts index a9c2587..cb5a25e 100644 --- a/server/src/interfaces/auth.ts +++ b/server/src/interfaces/auth.ts @@ -10,5 +10,5 @@ export interface UserTokenData { } export interface AuthedRequest extends Request { - user?: UserTokenData | JwtPayload | string | undefined + user?: UserTokenData } \ No newline at end of file diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index af0db15..4e95b48 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -7,11 +7,13 @@ import register from "./register" import center from "./center"; import trainer from "./trainer"; import timeslot from "./timeslot"; +import order from "./order"; router.use(login); router.use(register); router.use(center); router.use(trainer); router.use(timeslot); +router.use(order); export default router; \ No newline at end of file diff --git a/server/src/routes/order.ts b/server/src/routes/order.ts new file mode 100644 index 0000000..f98c58f --- /dev/null +++ b/server/src/routes/order.ts @@ -0,0 +1,156 @@ +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, client as pool } from "../db"; +import { DatabaseError } from "pg"; +import Trainer from "../interfaces/trainer"; +import { ReservedTimeslots, Timeslot, WeeklyTimeslot } from "../interfaces/timeslot"; +import { idSchema, timeSchema } from "../schemas"; +import { UserAuth } from "../middlewares/auth"; +import { AuthedRequest } from "../interfaces/auth"; + +dayjs.extend(isoWeek) +dayjs.extend(utc) + +const router: Router = express.Router(); + +const orderSchema = Joi.object({ + trainer: idSchema.required(), + startDate: Joi.date().required(), + endDate: Joi.date().required() +}); + +interface DatabaseResult { + trainer: Trainer + timeslots: WeeklyTimeslot[] + reserved_timeslots: ReservedTimeslots[] +} + +interface OrderBody { + trainer: number + startDate: Date + endDate: Date +} + +interface TimeslotValidQueryResult { + weekly_timeslot_available: boolean + time_already_reserved: boolean +} + +router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => { + try { + const client = await pool.connect(); + try { + const validation = orderSchema.validate(req.body, { abortEarly: false }); + if (validation.error !== undefined) { + return res.status(400).send(validation.error.details); + } + + const orderBody: OrderBody = validation.value; + + const startDate: dayjs.Dayjs = dayjs(orderBody.startDate).utcOffset(0); + const endDate: dayjs.Dayjs = dayjs(orderBody.endDate).utcOffset(0); + + const startTime: string = `${startDate.hour()}:${startDate.minute()}`; + const endTime: string = `${endDate.hour()}:${endDate.minute()}`; + + const weekday = startDate.isoWeekday(); + + await client.query("BEGIN"); + + const timeslotValidQuery = await client.query(` + select + EXISTS( + SELECT 1 FROM weekly_timeslots + WHERE + trainer_id = $1 + AND day_of_week = $2 + AND start_time = $3 + AND end_time = $4 + ) as weekly_timeslot_available, + EXISTS( + SELECT 1 FROM public.reserved_timeslots + WHERE ( + (start_time between $5 AND $6 + OR end_time between $5 AND $6)) + AND trainer_id = $1 + ) as time_already_reserved; + `, [ + orderBody.trainer, + weekday, + startTime, + endTime, + orderBody.startDate, + orderBody.endDate + ]); + + const timeslotValidQueryResult: TimeslotValidQueryResult = timeslotValidQuery.rows[0]; + + if (timeslotValidQueryResult.time_already_reserved || !timeslotValidQueryResult.weekly_timeslot_available) + return res.status(400).send([{ + message: "timeslot was not found", + path: [ + "startDate" + ], + type: "startDate.invalid" + }]); + + const priceQuery = await client.query(` + SELECT hourly_price FROM trainers WHERE id = $1; + `, [ + orderBody.trainer + ]); + + const hourlyPrice = priceQuery.rows[0].hourly_price; + + const hours = endDate.diff(startDate, "hours", true); + const price = Math.ceil(hours * hourlyPrice); + + const insertQuery = await client.query(` + WITH inserted_reserved_timeslot AS ( + INSERT INTO reserved_timeslots (trainer_id, start_time, end_time) + VALUES ($1, $2, $3) + RETURNING * + ) + + INSERT INTO orders (timeslot_id, user_id, order_status, price) + select id, $4, 'Processing', $5 + FROM inserted_reserved_timeslot + RETURNING id; + `, [ + orderBody.trainer, + orderBody.startDate, + orderBody.endDate, + req.user?.userId, + price + ]); + + const insertedData = insertQuery.rows[0]; + + await client.query("COMMIT"); + + return res.status(200).send({ + id: insertedData.id, + trainerId: orderBody.trainer, + startDate: orderBody.startDate, + endDate: orderBody.endDate, + price + }); + } catch (error: DatabaseError | Error | any) { + await client.query("ROLLBACK"); + console.error(error); + return res.sendStatus(500); + } + finally { + client.release(); + } + } catch (error: DatabaseError | Error | any) { + console.error(error); + return res.sendStatus(500); + } +}) + +export default router; \ No newline at end of file