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.

229 lines
7.8 KiB
TypeScript

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";
import { baseURL } from "../environment";
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
JOIN orders ON reserved_timeslots.id = orders.timeslot_id
WHERE
((start_time >= $5 AND start_time < $6)
OR (end_time > $5 AND end_time <= $6))
AND trainer_id = $1
AND (
orders.order_status = 'Confirmed'
OR orders.order_status = 'Created'
)
) 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 emailQuery = await client.query(`
SELECT email FROM users WHERE id = $1;
`, [
req.user?.userId
]);
const email: string = emailQuery.rows[0].email;
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: `${baseURL}/user/orders`,
cancel_url: `${baseURL}`,
customer_email: email
});
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;