Added order new time and time proposals endpoint
parent
37e9ad7489
commit
fbb0f60a23
@ -1,33 +1,33 @@
|
||||
# Endpoints
|
||||
|
||||
| Done | Method | Path | Description | Authentication |
|
||||
|------|----------|--------------------------|---------------------------------------------------|----------------|
|
||||
| [x] | POST | /login | Login | |
|
||||
| [x] | POST | /register | Create account | |
|
||||
| [x] | GET | /center | Get list of center | |
|
||||
| [x] | POST | /center | Admin can create center | Admin |
|
||||
| [x] | PUT | /center/:id | Admin can change center | Admin |
|
||||
| [ ] | DELETE | /center | Admin can delete center | Admin |
|
||||
| [x] | GET | /trainer | Get list of trainers | |
|
||||
| [x] | POST | /trainer | Admin can create trainer | Admin |
|
||||
| [ ] | 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 |
|
||||
| [x] | GET | /timeslot | Filter for available timeslots | |
|
||||
| [x] | GET | /trainer/timeslot | Trainer can get weekly timeslots | Trainer |
|
||||
| [x] | POST | /trainer/timeslot | Trainer can create weekly timeslots | Trainer |
|
||||
| [x] | DELETE | /trainer/timeslot/:id | Trainer can delete weekly timeslots | Trainer |
|
||||
| [ ] | GET | /trainer/order | Trainer can get reserved timeslots | Trainer |
|
||||
| [ ] | PUT | /trainer/order/:id | Trainer can change reserved timeslot | Trainer |
|
||||
| [ ] | DELTE | /trainer/order/:id | Trainer can delete reserved timeslot | Trainer |
|
||||
| [ ] | GET | /order | User can get list of orders | User |
|
||||
| [x] | 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 |
|
||||
| [ ] | PUT | /user | User can change profile information | User |
|
||||
| [ ] | GET | /verify_email | Verify email | |
|
||||
| [ ] | POST | /reset_password | Request password reset | |
|
||||
| [ ] | POST | /new_password | Set new password | |
|
||||
| Done | Method | Path | Description | Authentication |
|
||||
|------|----------|---------------------------------|---------------------------------------------------|----------------|
|
||||
| [x] | POST | /login | Login | |
|
||||
| [x] | POST | /register | Create account | |
|
||||
| [x] | GET | /center | Get list of center | |
|
||||
| [x] | POST | /center | Admin can create center | Admin |
|
||||
| [x] | PUT | /center/:id | Admin can change center | Admin |
|
||||
| [ ] | DELETE | /center | Admin can delete center | Admin |
|
||||
| [x] | GET | /trainer | Get list of trainers | |
|
||||
| [x] | POST | /trainer | Admin can create trainer | Admin |
|
||||
| [ ] | 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 |
|
||||
| [x] | GET | /timeslot | Filter for available timeslots | |
|
||||
| [x] | GET | /trainer/timeslot | Trainer can get weekly timeslots | Trainer |
|
||||
| [x] | POST | /trainer/timeslot | Trainer can create weekly timeslots | Trainer |
|
||||
| [x] | DELETE | /trainer/timeslot/:id | Trainer can delete weekly timeslots | Trainer |
|
||||
| [ ] | GET | /trainer/order | Trainer can get reserved timeslots | Trainer |
|
||||
| [ ] | PUT | /trainer/order/:id | Trainer can change reserved timeslot | Trainer |
|
||||
| [x] | GET | /trainer/order/:id/newtimeslots | Trainer can change reserved timeslot | Trainer |
|
||||
| [ ] | DELTE | /trainer/order/:id | Trainer can delete reserved timeslot | Trainer |
|
||||
| [x] | GET | /order | User can get list of orders | User |
|
||||
| [x] | POST | /order | User can request an order | User |
|
||||
| [x] | PUT | /order/:id | User can propose new time | User |
|
||||
| [x] | GET | /order/:id/newtimeslots | User can can get list of new timeslots | User |
|
||||
| [x] | POST | /order/:id/cancel | User can cancel order | User |
|
||||
| [x] | GET | /user | User can get profile information | User |
|
||||
| [ ] | PUT | /user | User can change profile information | User |
|
||||
| [ ] | GET | /verify_email | Verify email | |
|
||||
| [ ] | POST | /reset_password | Request password reset | |
|
||||
| [ ] | POST | /new_password | Set new password | |
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import Trainer from "./trainer";
|
||||
|
||||
export type OrderStatus = "Created" | "Confirmed" | "Failed" | "CancelledByTrainer" | "CancelledByUser";
|
||||
export type PaymentIntentStatus = "Created" | "Successful" | "Failed";
|
||||
|
||||
export interface OrderObject {
|
||||
id: number
|
||||
trainer: Trainer
|
||||
startDate: string
|
||||
endDate: string
|
||||
status: OrderObjectStatus
|
||||
price: number
|
||||
created_at: Date
|
||||
}
|
||||
|
||||
export type OrderObjectStatus = "Successful" | "Processing" | "CancelledByTrainer" | "CancelledByUser";
|
||||
@ -0,0 +1,214 @@
|
||||
import express, { Router, Response } from "express";
|
||||
import Joi from "joi"
|
||||
import dayjs, { Dayjs } from "dayjs"
|
||||
import isoWeek from "dayjs/plugin/isoWeek"
|
||||
import utc from "dayjs/plugin/utc"
|
||||
import LocalizedFormat from "dayjs/plugin/localizedFormat"
|
||||
import { } from "dayjs/locale/da";
|
||||
|
||||
import { client as pool } from "../db";
|
||||
import { DatabaseError } from "pg";
|
||||
import { idSchema } from "../schemas";
|
||||
import { UserAuth } from "../middlewares/auth";
|
||||
import { AuthedRequest } from "../interfaces/auth";
|
||||
import Stripe from 'stripe';
|
||||
import { stripe } from "../stripe";
|
||||
import Trainer from "../interfaces/trainer";
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(LocalizedFormat);
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
const orderSchema = Joi.object({
|
||||
trainer: idSchema.required(),
|
||||
startDate: Joi.date().min("now").required(),
|
||||
endDate: Joi.date().min(Joi.ref("startDate")).required()
|
||||
});
|
||||
|
||||
interface OrderBody {
|
||||
trainer: number
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}
|
||||
|
||||
interface TimeslotValidQueryResult {
|
||||
weekly_timeslot_available: boolean
|
||||
time_already_reserved: boolean
|
||||
}
|
||||
|
||||
function formatDate(date: Dayjs): string {
|
||||
let output: string = dayjs(date).locale("da").format("dddd D. MMM YYYY");
|
||||
let outputStringArray = output.split("");
|
||||
outputStringArray[0] = outputStringArray[0].toLocaleUpperCase();
|
||||
output = outputStringArray.join("");
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function formatTime(date: Dayjs): string {
|
||||
let output: string = dayjs(date).locale("da").format("HH:mm");
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
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 trainerLookup = await client.query(`
|
||||
SELECT trainers.id, first_name, last_name, center_id, centers.name as center_name FROM trainers
|
||||
JOIN users ON trainers.user_id = users.id
|
||||
JOIN centers on trainers.center_id = centers.id
|
||||
WHERE trainers.id = $1;
|
||||
`, [
|
||||
orderBody.trainer
|
||||
]);
|
||||
|
||||
if (trainerLookup.rows.length !== 1)
|
||||
return res.status(400).send([{
|
||||
message: "\"trainer\" was not found",
|
||||
path: [
|
||||
"trainer"
|
||||
],
|
||||
type: "trainer.not_found"
|
||||
}]);
|
||||
|
||||
const trainer: Trainer = trainerLookup.rows[0];
|
||||
|
||||
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 >= $5 AND start_time < $6)
|
||||
OR (end_time > $5 AND end_time <= $6))
|
||||
AND trainer_id = $1
|
||||
) as time_already_reserved;
|
||||
`, [
|
||||
orderBody.trainer,
|
||||
weekday,
|
||||
startTime,
|
||||
endTime,
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
]);
|
||||
|
||||
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 productName: string = `Personlig træningstime ${trainer.center_name}`;
|
||||
const productDescription: string = `Personlig træningstime med ${trainer.first_name} ${trainer.last_name} - ${formatDate(startDate)} ${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
|
||||
const checkoutSession: Stripe.Checkout.Session = await stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "DKK",
|
||||
unit_amount: price,
|
||||
product_data: {
|
||||
name: productName,
|
||||
description: productDescription,
|
||||
}
|
||||
},
|
||||
quantity: 1
|
||||
}
|
||||
],
|
||||
expires_at: Math.floor(Date.now()/1000 + 60*31),
|
||||
mode: 'payment',
|
||||
success_url: `http://localhost:5173/orders`,
|
||||
cancel_url: `http://localhost:5173`
|
||||
});
|
||||
|
||||
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, price, checkout_session)
|
||||
select id, $4, $5, $6
|
||||
FROM inserted_reserved_timeslot
|
||||
RETURNING id;
|
||||
`, [
|
||||
orderBody.trainer,
|
||||
startDate,
|
||||
endDate,
|
||||
req.user?.userId,
|
||||
price,
|
||||
checkoutSession.id
|
||||
]);
|
||||
|
||||
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,
|
||||
url: checkoutSession.url
|
||||
});
|
||||
} 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;
|
||||
@ -0,0 +1,162 @@
|
||||
import express, { Router, Response } from "express";
|
||||
import Joi from "joi"
|
||||
import dayjs, { Dayjs } from "dayjs"
|
||||
import isoWeek from "dayjs/plugin/isoWeek"
|
||||
import utc from "dayjs/plugin/utc"
|
||||
import LocalizedFormat from "dayjs/plugin/localizedFormat"
|
||||
import { } from "dayjs/locale/da";
|
||||
|
||||
import { client } from "../db";
|
||||
import { DatabaseError } from "pg";
|
||||
import { idSchema } from "../schemas";
|
||||
import { UserAuth } from "../middlewares/auth";
|
||||
import { AuthedRequest } from "../interfaces/auth";
|
||||
import Stripe from 'stripe';
|
||||
import { stripe } from "../stripe";
|
||||
import Trainer from "../interfaces/trainer";
|
||||
import { OrderStatus, PaymentIntentStatus } from "../interfaces/order";
|
||||
|
||||
dayjs.extend(isoWeek)
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(LocalizedFormat);
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
const orderSchema = Joi.object({
|
||||
startDate: Joi.date().min("now").required(),
|
||||
endDate: Joi.date().min(Joi.ref("startDate")).required()
|
||||
});
|
||||
|
||||
interface OrderBody {
|
||||
trainer: number
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}
|
||||
|
||||
interface TimeslotValidQueryResult {
|
||||
weekly_timeslot_available: boolean
|
||||
time_already_reserved: boolean
|
||||
}
|
||||
|
||||
interface OrderLookup {
|
||||
order_status: OrderStatus
|
||||
start_time: Date
|
||||
end_time: Date
|
||||
trainer_id: number
|
||||
timeslot_id: number
|
||||
}
|
||||
|
||||
router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) => {
|
||||
try {
|
||||
const lookupResult = await client.query(`
|
||||
SELECT
|
||||
order_status,
|
||||
start_time,
|
||||
end_time,
|
||||
trainer_id,
|
||||
timeslot_id
|
||||
FROM orders
|
||||
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
|
||||
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
|
||||
WHERE orders.id = $1
|
||||
AND user_id = $2
|
||||
AND order_status = 'Confirmed';
|
||||
`, [
|
||||
req.params.id,
|
||||
req.user?.userId
|
||||
]);
|
||||
|
||||
if (lookupResult.rows.length !== 1) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const order: OrderLookup = lookupResult.rows[0];
|
||||
|
||||
const originalStartDate: dayjs.Dayjs = dayjs(order.start_time).utcOffset(0);
|
||||
const originalEndDate: dayjs.Dayjs = dayjs(order.end_time).utcOffset(0);
|
||||
|
||||
/** The duration of the original timeslot in miliseconds */
|
||||
const originalDuration: number = originalEndDate.diff(originalStartDate);
|
||||
|
||||
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();
|
||||
|
||||
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 >= $5 AND start_time < $6)
|
||||
OR (end_time > $5 AND end_time <= $6))
|
||||
AND trainer_id = $1
|
||||
) as time_already_reserved;
|
||||
`, [
|
||||
order.trainer_id,
|
||||
weekday,
|
||||
startTime,
|
||||
endTime,
|
||||
startDate.toISOString(),
|
||||
endDate.toISOString()
|
||||
]);
|
||||
|
||||
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 duration = endDate.diff(startDate);
|
||||
|
||||
if (duration !== originalDuration)
|
||||
return res.status(400).send([{
|
||||
message: "timeslot is not same length as original",
|
||||
path: [
|
||||
"startDate"
|
||||
],
|
||||
type: "startDate.invalid"
|
||||
}]);
|
||||
|
||||
await client.query(`
|
||||
UPDATE reserved_timeslots SET
|
||||
start_time = $1,
|
||||
end_time = $2
|
||||
WHERE id = $3;
|
||||
`, [
|
||||
startDate,
|
||||
endDate,
|
||||
order.timeslot_id
|
||||
]);
|
||||
|
||||
return res.sendStatus(204);
|
||||
} catch (error: DatabaseError | Error | any) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
})
|
||||
|
||||
export default router;
|
||||
@ -0,0 +1,178 @@
|
||||
import express, { Router, Request, Response } from "express";
|
||||
import Joi from "joi";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { client } from "../db";
|
||||
import { DatabaseError } from "pg";
|
||||
import { UserAuth } from "../middlewares/auth";
|
||||
import { AuthedRequest } from "../interfaces/auth";
|
||||
import Trainer from "../interfaces/trainer";
|
||||
import { OrderObject, OrderObjectStatus, OrderStatus, PaymentIntentStatus } from "../interfaces/order";
|
||||
import { ReservedTimeslots, Timeslot, WeeklyTimeslot } from "../interfaces/timeslot";
|
||||
|
||||
const router: Router = express.Router();
|
||||
|
||||
interface OrderLookup {
|
||||
order_status: OrderStatus
|
||||
start_time: Date
|
||||
end_time: Date
|
||||
trainer_id: number
|
||||
timeslot_id: number
|
||||
}
|
||||
|
||||
interface TimeProposal {
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
|
||||
interface NewTimeslotPropsalFilter {
|
||||
startDate: Date
|
||||
endDate: Date
|
||||
}
|
||||
|
||||
const newTimeslotPropsalFilterSchema = Joi.object({
|
||||
startDate: Joi.date().default(Date.now()),
|
||||
endDate: Joi.date().default(Date.now())
|
||||
});
|
||||
|
||||
router.get("/order/:id/newtimeslots", UserAuth, async (req: AuthedRequest, res: Response) => {
|
||||
try {
|
||||
const lookupResult = await client.query(`
|
||||
SELECT
|
||||
order_status,
|
||||
start_time,
|
||||
end_time,
|
||||
trainer_id,
|
||||
timeslot_id
|
||||
FROM orders
|
||||
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
|
||||
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
|
||||
WHERE orders.id = $1
|
||||
AND user_id = $2
|
||||
AND order_status = 'Confirmed';
|
||||
`, [
|
||||
req.params.id,
|
||||
req.user?.userId
|
||||
]);
|
||||
|
||||
if (lookupResult.rows.length !== 1) {
|
||||
return res.sendStatus(404);
|
||||
}
|
||||
|
||||
const order: OrderLookup = lookupResult.rows[0];
|
||||
|
||||
const dateValiadtion = Joi.date().min(Date.now()).validate(order.start_time)
|
||||
if (dateValiadtion.error !== undefined) {
|
||||
return res.status(400).send({ message: "Timeslot has already occurred" });
|
||||
}
|
||||
|
||||
const filterValidation = newTimeslotPropsalFilterSchema.validate(req.query, { abortEarly: false, stripUnknown: true });
|
||||
if (filterValidation.error !== undefined) {
|
||||
return res.status(400).send(filterValidation.error.details);
|
||||
}
|
||||
|
||||
const newTimeslotPropsalFilter: NewTimeslotPropsalFilter = filterValidation.value;
|
||||
|
||||
const originalStartDate: dayjs.Dayjs = dayjs(order.start_time).utcOffset(0);
|
||||
const originalEndDate: dayjs.Dayjs = dayjs(order.end_time).utcOffset(0);
|
||||
|
||||
/** The duration of the original timeslot in miliseconds */
|
||||
const originalDuration: number = originalStartDate.diff(originalEndDate);
|
||||
|
||||
const latestAllowedFilterEndDate: dayjs.Dayjs = originalStartDate.add(1, "month");
|
||||
|
||||
const nowDate: dayjs.Dayjs = dayjs().utcOffset(0).startOf("date");
|
||||
|
||||
let filterStartDate: dayjs.Dayjs = dayjs(newTimeslotPropsalFilter.startDate).utcOffset(0).startOf("date");
|
||||
let filterEndDate: dayjs.Dayjs = dayjs(newTimeslotPropsalFilter.endDate).utcOffset(0).startOf("date");
|
||||
|
||||
if (filterStartDate.isBefore(nowDate)) {
|
||||
filterStartDate = nowDate;
|
||||
}
|
||||
|
||||
if (filterEndDate.isAfter(latestAllowedFilterEndDate)) {
|
||||
filterEndDate = latestAllowedFilterEndDate;
|
||||
}
|
||||
|
||||
let weekdays: number[] = [];
|
||||
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")) {
|
||||
if (!weekdays.includes(day.isoWeekday()))
|
||||
weekdays.push(day.isoWeekday());
|
||||
}
|
||||
|
||||
const weeklyTimeslotLookup = await client.query(`
|
||||
SELECT
|
||||
id,
|
||||
day_of_week,
|
||||
start_time,
|
||||
end_time
|
||||
FROM weekly_timeslots
|
||||
WHERE trainer_id = $1
|
||||
AND day_of_week = ANY($2);
|
||||
`, [
|
||||
order.trainer_id,
|
||||
weekdays
|
||||
]);
|
||||
|
||||
const weeklyTimeslots: WeeklyTimeslot[] = weeklyTimeslotLookup.rows;
|
||||
|
||||
const reservedTimeslotLookup = await client.query(`
|
||||
SELECT
|
||||
start_time,
|
||||
end_time
|
||||
FROM public.reserved_timeslots
|
||||
JOIN orders ON reserved_timeslots.id = orders.timeslot_id
|
||||
WHERE
|
||||
(start_time >= $1 AND end_time <= $2)
|
||||
AND reserved_timeslots.trainer_id = $3
|
||||
AND (
|
||||
orders.order_status = 'Confirmed'
|
||||
OR orders.order_status = 'Created'
|
||||
);
|
||||
`, [
|
||||
filterStartDate,
|
||||
filterEndDate.add(1, "day"),
|
||||
order.trainer_id
|
||||
]);
|
||||
|
||||
const reservedTimeslots: ReservedTimeslots[] = reservedTimeslotLookup.rows;
|
||||
|
||||
const timeProposals: TimeProposal[] = [];
|
||||
|
||||
for (let day: dayjs.Dayjs = filterStartDate; !day.isAfter(filterEndDate); day = day.add(1, "day")){
|
||||
const weekDay = day.isoWeekday();
|
||||
|
||||
weeklyTimeslots: for (const weeklyTimeslot of weeklyTimeslots) {
|
||||
if (weeklyTimeslot.day_of_week !== weekDay)
|
||||
continue;
|
||||
|
||||
const startTime = dayjs(`${day.toISOString().split("T")[0]}T${weeklyTimeslot.start_time}`);
|
||||
const endTime = dayjs(`${day.toISOString().split("T")[0]}T${weeklyTimeslot.end_time}`);
|
||||
for (const reservedTimeslot of reservedTimeslots) {
|
||||
const reservedTimeStart = dayjs(reservedTimeslot.start_time);
|
||||
const reservedTimeEnd = dayjs(reservedTimeslot.end_time);
|
||||
if ((!reservedTimeStart.isBefore(startTime) && reservedTimeStart.isBefore(endTime)) ||
|
||||
(reservedTimeEnd.isAfter(startTime) && !reservedTimeEnd.isAfter(endTime))) {
|
||||
continue weeklyTimeslots;
|
||||
}
|
||||
}
|
||||
|
||||
const proposedDuration: number = startTime.diff(endTime);
|
||||
if (proposedDuration !== originalDuration)
|
||||
continue;
|
||||
|
||||
timeProposals.push({
|
||||
startDate: `${day.toISOString().split("T")[0]}T${weeklyTimeslot.start_time}Z`,
|
||||
endDate: `${day.toISOString().split("T")[0]}T${weeklyTimeslot.end_time}Z`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send(timeProposals);
|
||||
} catch (error: DatabaseError | Error | any) {
|
||||
console.error(error);
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Loading…
Reference in New Issue