Converts booking deletion to soft-delete + more robust cancellation (#581)
* Converts booking deletion to soft-delete + more robust cancellation * Update pages/api/cancel.ts infer type :) Co-authored-by: Alex Johansson <alexander@n1s.se> Co-authored-by: Alex Johansson <alexander@n1s.se> Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
This commit is contained in:
parent
e48318a34b
commit
8ee68e2ace
4 changed files with 49 additions and 19 deletions
|
@ -1,15 +1,17 @@
|
||||||
import prisma from "../../lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
import { deleteEvent } from "../../lib/calendarClient";
|
import { deleteEvent } from "@lib/calendarClient";
|
||||||
import async from "async";
|
import async from "async";
|
||||||
import { deleteMeeting } from "../../lib/videoClient";
|
import { deleteMeeting } from "@lib/videoClient";
|
||||||
|
import { asStringOrNull } from "@lib/asStringOrNull";
|
||||||
|
import { BookingStatus } from "@prisma/client";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
if (req.method == "POST") {
|
if (req.method == "POST") {
|
||||||
const uid = req.body.uid;
|
const uid = asStringOrNull(req.body.uid);
|
||||||
|
|
||||||
const bookingToDelete = await prisma.booking.findFirst({
|
const bookingToDelete = await prisma.booking.findUnique({
|
||||||
where: {
|
where: {
|
||||||
uid: uid,
|
uid,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -28,6 +30,21 @@ export default async function handler(req, res) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!bookingToDelete) {
|
||||||
|
return res.status(404).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// by cancelling first, and blocking whilst doing so; we can ensure a cancel
|
||||||
|
// action always succeeds even if subsequent integrations fail cancellation.
|
||||||
|
await prisma.booking.update({
|
||||||
|
where: {
|
||||||
|
uid,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
status: BookingStatus.CANCELLED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const apiDeletes = async.mapLimit(bookingToDelete.user.credentials, 5, async (credential) => {
|
const apiDeletes = async.mapLimit(bookingToDelete.user.credentials, 5, async (credential) => {
|
||||||
const bookingRefUid = bookingToDelete.references.filter((ref) => ref.type === credential.type)[0]?.uid;
|
const bookingRefUid = bookingToDelete.references.filter((ref) => ref.type === credential.type)[0]?.uid;
|
||||||
if (bookingRefUid) {
|
if (bookingRefUid) {
|
||||||
|
@ -38,23 +55,20 @@ export default async function handler(req, res) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const attendeeDeletes = prisma.attendee.deleteMany({
|
const attendeeDeletes = prisma.attendee.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
bookingId: bookingToDelete.id,
|
bookingId: bookingToDelete.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
bookingId: bookingToDelete.id,
|
bookingId: bookingToDelete.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const bookingDeletes = prisma.booking.delete({
|
|
||||||
where: {
|
|
||||||
id: bookingToDelete.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all([apiDeletes, attendeeDeletes, bookingReferenceDeletes, bookingDeletes]);
|
await Promise.all([apiDeletes, attendeeDeletes, bookingReferenceDeletes]);
|
||||||
|
|
||||||
//TODO Perhaps send emails to user and client to tell about the cancellation
|
//TODO Perhaps send emails to user and client to tell about the cancellation
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import classNames from "@lib/classNames";
|
||||||
import { ClockIcon, XIcon } from "@heroicons/react/outline";
|
import { ClockIcon, XIcon } from "@heroicons/react/outline";
|
||||||
import Loader from "@components/Loader";
|
import Loader from "@components/Loader";
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
|
import { BookingStatus } from "@prisma/client";
|
||||||
|
|
||||||
export default function Bookings({ bookings }) {
|
export default function Bookings({ bookings }) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
@ -44,8 +45,7 @@ export default function Bookings({ bookings }) {
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{bookings
|
{bookings
|
||||||
.filter((booking) => !booking.confirmed && !booking.rejected)
|
.filter((booking) => booking.status !== BookingStatus.CANCELLED)
|
||||||
.concat(bookings.filter((booking) => booking.confirmed || booking.rejected))
|
|
||||||
.map((booking) => (
|
.map((booking) => (
|
||||||
<tr key={booking.id}>
|
<tr key={booking.id}>
|
||||||
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
|
<td className={"px-6 py-4" + (booking.rejected ? " line-through" : "")}>
|
||||||
|
@ -66,11 +66,13 @@ export default function Bookings({ bookings }) {
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{booking.attendees.length !== 0 && (
|
||||||
<div className="text-sm text-blue-500">
|
<div className="text-sm text-blue-500">
|
||||||
<a href={"mailto:" + booking.attendees[0].email}>
|
<a href={"mailto:" + booking.attendees[0].email}>
|
||||||
{booking.attendees[0].email}
|
{booking.attendees[0].email}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
|
<td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
|
||||||
<div className="text-sm text-gray-900">
|
<div className="text-sm text-gray-900">
|
||||||
|
@ -232,6 +234,7 @@ export async function getServerSideProps(context) {
|
||||||
id: true,
|
id: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
endTime: true,
|
endTime: true,
|
||||||
|
status: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
startTime: "asc",
|
startTime: "asc",
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "BookingStatus" AS ENUM ('cancelled', 'accepted', 'rejected', 'pending');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Booking" ADD COLUMN "status" "BookingStatus" NOT NULL DEFAULT E'accepted';
|
|
@ -136,6 +136,13 @@ model Attendee {
|
||||||
bookingId Int?
|
bookingId Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BookingStatus {
|
||||||
|
CANCELLED @map("cancelled")
|
||||||
|
ACCEPTED @map("accepted")
|
||||||
|
REJECTED @map("rejected")
|
||||||
|
PENDING @map("pending")
|
||||||
|
}
|
||||||
|
|
||||||
model Booking {
|
model Booking {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
uid String @unique
|
uid String @unique
|
||||||
|
@ -157,6 +164,7 @@ model Booking {
|
||||||
updatedAt DateTime?
|
updatedAt DateTime?
|
||||||
confirmed Boolean @default(true)
|
confirmed Boolean @default(true)
|
||||||
rejected Boolean @default(false)
|
rejected Boolean @default(false)
|
||||||
|
status BookingStatus @default(ACCEPTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
model Schedule {
|
model Schedule {
|
||||||
|
|
Loading…
Reference in a new issue