Added user email notifications
continuous-integration/drone/push Build is passing Details

main
Filip Borum Poulsen 3 years ago
parent fd966bf208
commit 11bba98ae4

@ -9,3 +9,7 @@ PRIVATE_KEY_LOCATION=/data/cert/private.pem
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
BASE_URL=https://merit.bpfilip.dk
SMTP_HOSTNAME=email-smtp.eu-central-1.amazonaws.com
SMTP_USERNAME=user123
SMTP_PASSWORD=password12345
SMTP_ADDRESS=user@example.com

@ -16,6 +16,7 @@
"express": "^4.18.2",
"joi": "^17.9.1",
"jsonwebtoken": "^9.0.0",
"nodemailer": "^6.9.1",
"pg": "^8.10.0",
"stripe": "^12.1.1"
},
@ -25,6 +26,7 @@
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.1",
"@types/node": "^18.15.11",
"@types/nodemailer": "^6.4.7",
"@types/pg": "^8.6.6",
"concurrently": "^8.0.1",
"nodemon": "^2.0.22",
@ -249,8 +251,16 @@
"node_modules/@types/node": {
"version": "18.15.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==",
"dev": true
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
},
"node_modules/@types/nodemailer": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz",
"integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/pg": {
"version": "8.6.6",
@ -1469,6 +1479,14 @@
}
}
},
"node_modules/nodemailer": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz",
"integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
"version": "2.0.22",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",

@ -21,6 +21,7 @@
"express": "^4.18.2",
"joi": "^17.9.1",
"jsonwebtoken": "^9.0.0",
"nodemailer": "^6.9.1",
"pg": "^8.10.0",
"stripe": "^12.1.1"
},
@ -30,6 +31,7 @@
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.1",
"@types/node": "^18.15.11",
"@types/nodemailer": "^6.4.7",
"@types/pg": "^8.6.6",
"concurrently": "^8.0.1",
"nodemon": "^2.0.22",

@ -43,3 +43,7 @@ export const stripeWebhookSecret: string = process.env.STRIPE_WEBHOOK_SECRET;
export const baseURL: string = process.env.BASE_URL;
export const smtpHostname: string | undefined = process.env.SMTP_HOSTNAME;
export const smtpUsername: string | undefined = process.env.SMTP_USERNAME;
export const smtpPassword: string | undefined = process.env.SMTP_PASSWORD;
export const smtpAddress: string | undefined = process.env.SMTP_ADDRESS;

@ -1,4 +1,6 @@
import { Dayjs } from "dayjs";
import Trainer from "./trainer";
import User from "./user";
export type OrderStatus = "Created" | "Confirmed" | "Failed" | "CancelledByTrainer" | "CancelledByUser";
export type PaymentIntentStatus = "Created" | "Successful" | "Failed";
@ -13,4 +15,14 @@ export interface OrderObject {
created_at: Date
}
export interface Order {
id: number
trainer: Trainer
startDate: Date|Dayjs
endDate: Date|Dayjs
status: OrderObjectStatus
price: number
created_at: Date
}
export type OrderObjectStatus = "Successful" | "Processing" | "CancelledByTrainer" | "CancelledByUser";

@ -3,6 +3,6 @@ interface User {
id: number
first_name: string
last_name: string
password_hash: string
password_hash?: string
}
export default User;

@ -0,0 +1,13 @@
import nodemailer from "nodemailer";
import { smtpHostname, smtpPassword, smtpUsername } from "../environment";
export const transporter = nodemailer.createTransport({
host: smtpHostname,
port: 587,
secure: false,
requireTLS: true,
auth: {
user: smtpUsername,
pass: smtpPassword,
}
});

@ -0,0 +1,21 @@
import { transporter } from ".";
import { smtpAddress } from "../environment";
import { Order, OrderObject } from "../interfaces/order";
import User from "../interfaces/user";
import { formatDate, formatTime } from "../utils/dates";
export async function sendOrderConfirmationEmail(user: User, order: Order) {
await transporter.sendMail({
from: `Fitness World <${smtpAddress}>`,
to: user.email,
subject: "Ordre bekræftelse - Fitness World",
text:
`Hej, ${user.first_name} ${user.last_name}.
Din tid ved ${order.trainer.first_name} ${order.trainer.last_name} i ${order.trainer.center_name} er blevet reserveret.
Vi glæder os til at se dig ${formatDate(order.startDate)} ${formatTime(order.startDate)} - ${formatTime(order.endDate)}
Venlig hilsen Fitness World.`
})
}

@ -0,0 +1,15 @@
import { transporter } from ".";
import { smtpAddress } from "../environment";
import User from "../interfaces/user";
export async function sendSignupEmail(user: User) {
await transporter.sendMail({
from: `Fitness World <${smtpAddress}>`,
to: user.email,
subject: "Welcome to Fitness World",
text:
`Hej, ${user.first_name} ${user.last_name}.
Velkommen til Fitness World.`
})
}

@ -0,0 +1,21 @@
import { transporter } from ".";
import { smtpAddress } from "../environment";
import { Order } from "../interfaces/order";
import User from "../interfaces/user";
import { formatDate, formatTime } from "../utils/dates";
export async function sendUserCancelledOrderEmail(user: User, order: Order) {
await transporter.sendMail({
from: `Fitness World <${smtpAddress}>`,
to: user.email,
subject: "Ordre afbestilt - Fitness World",
text:
`Hej, ${user.first_name} ${user.last_name}.
Du har afbestilt din tid ved ${order.trainer.first_name} ${order.trainer.last_name} i ${order.trainer.center_name} ${formatDate(order.startDate)} ${formatTime(order.startDate)} - ${formatTime(order.endDate)}
Vi håber at se dig en anden dag.
Venlig hilsen Fitness World.`
})
}

@ -0,0 +1,21 @@
import { transporter } from ".";
import { smtpAddress } from "../environment";
import { Order, OrderObject } from "../interfaces/order";
import User from "../interfaces/user";
import { formatDate, formatTime } from "../utils/dates";
export async function sendUserMovedOrderEmail(user: User, order: Order) {
await transporter.sendMail({
from: `Fitness World <${smtpAddress}>`,
to: user.email,
subject: "Ordre flyttet - Fitness World",
text:
`Hej, ${user.first_name} ${user.last_name}.
Din tid ved ${order.trainer.first_name} ${order.trainer.last_name} i ${order.trainer.center_name} er blevet flyttet.
Vi glæder os til at se dig ${formatDate(order.startDate)} ${formatTime(order.startDate)} - ${formatTime(order.endDate)}
Venlig hilsen Fitness World.`
})
}

@ -8,13 +8,13 @@ async function main() {
const users = await client.query(`
INSERT INTO users (first_name, last_name, email, password_hash, email_verified, is_admin) VALUES
('Filip', 'B P', 'fbp@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, true),
('User1', 'Lastname', 'u1@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('User2', 'Lastname', 'u2@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('User3', 'Lastname', 'u3@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Trainer1', 'Lastname', 't1@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Trainer2', 'Lastname', 't2@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Admin1', 'Lastname', 'a1@test.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, true)
('Filip', 'B P', 'bpfilip@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, true),
('User1', 'Lastname', 'bpfilip+u1@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('User2', 'Lastname', 'bpfilip+u2@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('User3', 'Lastname', 'bpfilip+u3@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Trainer1', 'Lastname', 'bpfilip+t1@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Trainer2', 'Lastname', 'bpfilip+t2@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, false),
('Admin1', 'Lastname', 'bpfilip+a1@gmail.com', '$2b$10$zxqwqZXo3DrLVTFx.hkQQ.uKeqhHMnok.G/4.Ivq1g647RaqYtgKC', true, true)
RETURNING id, email;
`);
@ -31,9 +31,9 @@ INSERT INTO trainers (user_id, center_id, hourly_price) VALUES
($3, $4, 20000)
RETURNING id, user_id;
`, [
users.rows.find(user => user.email === "t1@test.com").id,
users.rows.find(user => user.email === "bpfilip+t1@gmail.com").id,
centers.rows[0].id,
users.rows.find(user => user.email === "t2@test.com").id,
users.rows.find(user => user.email === "bpfilip+t2@gmail.com").id,
centers.rows[1].id,
]);
@ -81,8 +81,8 @@ INSERT INTO orders (timeslot_id, user_id, order_status, price, checkout_session)
($7, $1, 'Failed', 20000, '')
RETURNING id;
`, [
users.rows.find(user => user.email === "u1@test.com").id,
users.rows.find(user => user.email === "u2@test.com").id,
users.rows.find(user => user.email === "bpfilip+u1@gmail.com").id,
users.rows.find(user => user.email === "bpfilip+u2@gmail.com").id,
reserved_timeslots.rows[0].id,
reserved_timeslots.rows[1].id,
reserved_timeslots.rows[2].id,

@ -1,10 +1,6 @@
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";
@ -15,10 +11,10 @@ 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);
import { sendOrderConfirmationEmail } from "../mail/orderConfirmation";
import User from "../interfaces/user";
import { Order } from "../interfaces/order";
import { formatDate, formatTime } from "../utils/dates";
const router: Router = express.Router();
@ -39,21 +35,6 @@ interface TimeslotValidQueryResult {
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();
@ -137,13 +118,18 @@ router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => {
type: "startDate.invalid"
}]);
const emailQuery = await client.query(`
SELECT email FROM users WHERE id = $1;
const userQuery = await client.query(`
SELECT
id,
email,
first_name,
last_name
FROM users WHERE id = $1;
`, [
req.user?.userId
]);
const email: string = emailQuery.rows[0].email;
const user: User = userQuery.rows[0];
const priceQuery = await client.query(`
SELECT hourly_price FROM trainers WHERE id = $1;
@ -177,7 +163,7 @@ router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => {
mode: 'payment',
success_url: `${baseURL}/user/orders`,
cancel_url: `${baseURL}`,
customer_email: email
customer_email: user.email
});
const insertQuery = await client.query(`
@ -190,7 +176,7 @@ router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => {
INSERT INTO orders (timeslot_id, user_id, price, checkout_session)
select id, $4, $5, $6
FROM inserted_reserved_timeslot
RETURNING id;
RETURNING id, created_at;
`, [
orderBody.trainer,
startDate,
@ -202,10 +188,20 @@ router.post("/order", UserAuth, async (req: AuthedRequest, res: Response) => {
const insertedData = insertQuery.rows[0];
const order: Order = {
id: insertedData.id,
startDate: orderBody.startDate,
endDate: orderBody.endDate,
status: "Processing",
price,
trainer,
created_at: insertedData.created_at
};
await client.query("COMMIT");
return res.status(200).send({
id: insertedData.id,
id: order.id,
trainerId: orderBody.trainer,
startDate: orderBody.startDate,
endDate: orderBody.endDate,

@ -7,7 +7,9 @@ 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 { Order, OrderObject, OrderObjectStatus, OrderStatus, PaymentIntentStatus } from "../interfaces/order";
import { sendUserCancelledOrderEmail } from "../mail/userCancelledOrder";
import User from "../interfaces/user";
const router: Router = express.Router();
@ -20,15 +22,36 @@ interface CancelOrderLookup {
router.post("/order/:id/cancel", UserAuth, async (req: AuthedRequest, res: Response) => {
try {
const lookupResult = await client.query(`
SELECT
order_status,
payment_intents.status as payment_intent_status,
start_time
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
SELECT
orders.id,
order_status,
price,
created_at,
payment_intents.status as payment_intent_status,
start_time as "startDate",
end_time as "endDate",
json_build_object(
'id',trainers.id,
'first_name',trainer_user.first_name,
'last_name',trainer_user.last_name,
'center_id',trainers.center_id,
'center_name',centers.name
) as trainer,
json_build_object(
'id', order_user.id,
'first_name', order_user.first_name,
'last_name', order_user.last_name,
'email', order_user.email
) as user
FROM orders
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
LEFT JOIN trainers ON trainers.id = reserved_timeslots.trainer_id
LEFT JOIN centers on trainers.center_id = centers.id
LEFT JOIN users AS trainer_user on trainer_user.id = trainers.user_id
LEFT JOIN users AS order_user on order_user.id = orders.user_id
WHERE orders.id = $1
AND orders.user_id = $2
AND order_status = 'Confirmed';
`, [
req.params.id,
@ -39,9 +62,11 @@ router.post("/order/:id/cancel", UserAuth, async (req: AuthedRequest, res: Respo
return res.sendStatus(404);
}
const order: CancelOrderLookup = lookupResult.rows[0];
const user: User = lookupResult.rows[0].user;
lookupResult.rows[0].user = undefined;
const order: Order = lookupResult.rows[0];
const dateValidation = Joi.date().min(Date.now()).validate(order.start_time)
const dateValidation = Joi.date().min(Date.now()).validate(order.startDate)
if (dateValidation.error !== undefined) {
return res.status(400).send({ message: "Timeslot has already occurred" });
}
@ -55,6 +80,8 @@ router.post("/order/:id/cancel", UserAuth, async (req: AuthedRequest, res: Respo
req.params.id
]);
sendUserCancelledOrderEmail(user, order);
return res.sendStatus(204);
} catch (error: DatabaseError | Error | any) {
console.error(error);

@ -14,7 +14,10 @@ import { AuthedRequest } from "../interfaces/auth";
import Stripe from 'stripe';
import { stripe } from "../stripe";
import Trainer from "../interfaces/trainer";
import { Order } from "../interfaces/order";
import { OrderStatus, PaymentIntentStatus } from "../interfaces/order";
import { sendUserMovedOrderEmail } from "../mail/userMovedOrder";
import User from "../interfaces/user";
dayjs.extend(isoWeek)
dayjs.extend(utc)
@ -49,17 +52,37 @@ interface OrderLookup {
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
SELECT
orders.id,
order_status,
price,
created_at,
payment_intents.status as payment_intent_status,
start_time as "startDate",
end_time as "endDate",
timeslot_id,
json_build_object(
'id',trainers.id,
'first_name',trainer_user.first_name,
'last_name',trainer_user.last_name,
'center_id',trainers.center_id,
'center_name',centers.name
) as trainer,
json_build_object(
'id', order_user.id,
'first_name', order_user.first_name,
'last_name', order_user.last_name,
'email', order_user.email
) as user
FROM orders
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
LEFT JOIN payment_intents ON payment_intents.id = orders.payment_intent
LEFT JOIN trainers ON trainers.id = reserved_timeslots.trainer_id
LEFT JOIN centers on trainers.center_id = centers.id
LEFT JOIN users AS trainer_user on trainer_user.id = trainers.user_id
LEFT JOIN users AS order_user on order_user.id = orders.user_id
WHERE orders.id = $1
AND orders.user_id = $2
AND order_status = 'Confirmed';
`, [
req.params.id,
@ -70,10 +93,14 @@ router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) =>
return res.sendStatus(404);
}
const order: OrderLookup = lookupResult.rows[0];
const user: User = lookupResult.rows[0].user;
lookupResult.rows[0].user = undefined;
const timeslot_id: number = lookupResult.rows[0].timeslot_id;
lookupResult.rows[0].timeslot_id = undefined;
const order: Order = lookupResult.rows[0];
const originalStartDate: dayjs.Dayjs = dayjs(order.start_time).utcOffset(0);
const originalEndDate: dayjs.Dayjs = dayjs(order.end_time).utcOffset(0);
const originalStartDate: dayjs.Dayjs = dayjs(order.startDate).utcOffset(0);
const originalEndDate: dayjs.Dayjs = dayjs(order.endDate).utcOffset(0);
/** The duration of the original timeslot in miliseconds */
const originalDuration: number = originalEndDate.diff(originalStartDate);
@ -83,7 +110,7 @@ router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) =>
return res.status(400).send(validation.error.details);
}
const dateValidation = Joi.date().min(Date.now()).validate(order.start_time)
const dateValidation = Joi.date().min(Date.now()).validate(order.startDate)
if (dateValidation.error !== undefined) {
return res.status(400).send({ message: "Timeslot has already occurred" });
}
@ -121,7 +148,7 @@ router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) =>
)
) as time_already_reserved;
`, [
order.trainer_id,
order.trainer.id,
weekday,
startTime,
endTime,
@ -159,9 +186,14 @@ router.put("/order/:id", UserAuth, async (req: AuthedRequest, res: Response) =>
`, [
startDate,
endDate,
order.timeslot_id
timeslot_id
]);
order.startDate = startDate;
order.endDate = endDate;
sendUserMovedOrderEmail(user, order);
return res.sendStatus(204);
} catch (error: DatabaseError | Error | any) {
console.error(error);

@ -8,6 +8,8 @@ import jsonwebtoken, { JsonWebTokenError } from "jsonwebtoken";
import { DatabaseError } from "pg";
import { UserTokenData } from "../interfaces/auth";
import { private_key } from "../environment"
import User from "../interfaces/user";
import { sendSignupEmail } from "../mail/signUp";
const router: Router = express.Router();
@ -32,7 +34,7 @@ router.post("/register", async (req: Request, res: Response) => {
const insertResult = await client.query(`
INSERT INTO users (first_name, last_name, email, password_hash)
VALUES ($1, $2, $3, $4)
RETURNING id;
RETURNING id, first_name, last_name, email;
`, [
userData.firstname,
userData.lastname,
@ -40,7 +42,7 @@ RETURNING id;
password_hash
]);
const user = insertResult.rows[0];
const user: User = insertResult.rows[0];
const jwtData: UserTokenData = {
tokenType: "User",
@ -51,6 +53,8 @@ RETURNING id;
res.cookie("auth-token", jwt, { httpOnly: true, maxAge: 60 * 60 * 4 });
sendSignupEmail(user);
return res.status(200).send({ ...userData, password: undefined });
} catch (error: DatabaseError | Error | any) {
if (error.constraint == "users_email_key") {

@ -0,0 +1,24 @@
import dayjs, { Dayjs } from "dayjs"
import isoWeek from "dayjs/plugin/isoWeek"
import utc from "dayjs/plugin/utc"
import LocalizedFormat from "dayjs/plugin/localizedFormat"
import localeDA from "dayjs/locale/da";
dayjs.extend(isoWeek)
dayjs.extend(utc)
dayjs.extend(LocalizedFormat);
export function formatDate(date: Date | Dayjs): string {
let output: string = dayjs(date).locale(localeDA).format("dddd D. MMM YYYY");
let outputStringArray = output.split("");
outputStringArray[0] = outputStringArray[0].toLocaleUpperCase();
output = outputStringArray.join("");
return output;
}
export function formatTime(date: Date | Dayjs): string {
let output: string = dayjs(date).locale(localeDA).format("HH:mm");
return output;
}

@ -1,6 +1,9 @@
import { client } from "../db";
import Stripe from 'stripe';
import { sendOrderConfirmationEmail } from "../mail/orderConfirmation";
import User from "../interfaces/user";
import { Order } from "../interfaces/order";
const PaymentIntentStatusMap = {
"paid": "Successful",
@ -38,6 +41,7 @@ export default async function checkoutSessionCompleted(event: Stripe.Event) {
paymentIntentId,
session.id
]);
await client.query(`
UPDATE orders SET
order_status = 'Confirmed'
@ -47,4 +51,52 @@ export default async function checkoutSessionCompleted(event: Stripe.Event) {
`, [
session.id
]);
const orderLookup = await client.query(`
SELECT
orders.id,
order_status,
price,
start_time,
end_time,
json_build_object(
'id',trainers.id,
'first_name',users.first_name,
'last_name',users.last_name,
'center_id',trainers.center_id,
'center_name',centers.name
) as trainer
FROM orders
LEFT JOIN reserved_timeslots ON reserved_timeslots.id = orders.timeslot_id
LEFT JOIN trainers ON trainers.id = reserved_timeslots.trainer_id
LEFT JOIN centers on trainers.center_id = centers.id
LEFT JOIN users on users.id = trainers.user_id
WHERE
checkout_session = $1;
`, [
session.id
]);
const order: Order = orderLookup.rows[0];
order.startDate = orderLookup.rows[0].start_time;
order.endDate = orderLookup.rows[0].end_time;
const userLookup = await client.query(`
SELECT
users.email,
users.id,
users.first_name,
users.last_name
FROM orders
LEFT JOIN users on users.id = orders.user_id
WHERE
checkout_session = $1;
`, [
session.id
]);
const user: User = userLookup.rows[0];
sendOrderConfirmationEmail(user, order);
}
Loading…
Cancel
Save