Booking confirm endpoint refactoring (#2949)
* Adds new default handler and responder * Moved confirm endpoint * Fixes availability for unconfirmed bookings * Cleanup * Update _patch.ts * Prevent too much diffs * Adds missing BookingStatus * Migrates confirmed & rejected to status * Adds requiresConfirmation icon to listing * Adds booking status migration * Adds migrations to remove confirmed/rejected * Undo refactor * Sets the organizer as "accepted" in gCal * Update getBusyTimes.ts Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
3b321e5d3c
commit
12d66cb9df
|
@ -7,6 +7,7 @@
|
||||||
"bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
|
"bradlc.vscode-tailwindcss", // hinting / autocompletion for tailwind
|
||||||
"ban.spellright", // Spell check for docs
|
"ban.spellright", // Spell check for docs
|
||||||
"stripe.vscode-stripe", // stripe VSCode extension
|
"stripe.vscode-stripe", // stripe VSCode extension
|
||||||
"Prisma.prisma" // syntax|format|completion for prisma
|
"Prisma.prisma", // syntax|format|completion for prisma
|
||||||
|
"rebornix.project-snippets" // Share useful snippets between collaborators
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,9 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
);
|
);
|
||||||
const isUpcoming = new Date(booking.endTime) >= new Date();
|
const isUpcoming = new Date(booking.endTime) >= new Date();
|
||||||
const isCancelled = booking.status === BookingStatus.CANCELLED;
|
const isCancelled = booking.status === BookingStatus.CANCELLED;
|
||||||
|
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
|
||||||
|
const isRejected = booking.status === BookingStatus.REJECTED;
|
||||||
|
const isPending = booking.status === BookingStatus.PENDING;
|
||||||
|
|
||||||
const pendingActions: ActionType[] = [
|
const pendingActions: ActionType[] = [
|
||||||
{
|
{
|
||||||
|
@ -205,7 +208,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
if (location.includes("integration")) {
|
if (location.includes("integration")) {
|
||||||
if (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) {
|
if (booking.status === BookingStatus.CANCELLED || booking.status === BookingStatus.REJECTED) {
|
||||||
location = t("web_conference");
|
location = t("web_conference");
|
||||||
} else if (booking.confirmed) {
|
} else if (isConfirmed) {
|
||||||
location = linkValueToString(booking.location, t);
|
location = linkValueToString(booking.location, t);
|
||||||
} else {
|
} else {
|
||||||
location = t("web_conferencing_details_to_follow");
|
location = t("web_conferencing_details_to_follow");
|
||||||
|
@ -227,7 +230,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
eventName: booking.eventType.eventName || "",
|
eventName: booking.eventType.eventName || "",
|
||||||
bookingId: booking.id,
|
bookingId: booking.id,
|
||||||
recur: booking.recurringEventId,
|
recur: booking.recurringEventId,
|
||||||
reschedule: booking.confirmed,
|
reschedule: isConfirmed,
|
||||||
listingStatus: booking.listingStatus,
|
listingStatus: booking.listingStatus,
|
||||||
status: booking.status,
|
status: booking.status,
|
||||||
},
|
},
|
||||||
|
@ -322,14 +325,10 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td className={"flex-1 ltr:pl-4 rtl:pr-4" + (isRejected ? " line-through" : "")} onClick={onClick}>
|
||||||
className={"flex-1 ltr:pl-4 rtl:pr-4" + (booking.rejected ? " line-through" : "")}
|
|
||||||
onClick={onClick}>
|
|
||||||
<div className="cursor-pointer py-4">
|
<div className="cursor-pointer py-4">
|
||||||
<div className="sm:hidden">
|
<div className="sm:hidden">
|
||||||
{!booking.confirmed && !booking.rejected && (
|
{isPending && <Tag className="mb-2 ltr:mr-2 rtl:ml-2">{t("unconfirmed")}</Tag>}
|
||||||
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">{t("unconfirmed")}</Tag>
|
|
||||||
)}
|
|
||||||
{!!booking?.eventType?.price && !booking.paid && (
|
{!!booking?.eventType?.price && !booking.paid && (
|
||||||
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">Pending payment</Tag>
|
<Tag className="mb-2 ltr:mr-2 rtl:ml-2">Pending payment</Tag>
|
||||||
)}
|
)}
|
||||||
|
@ -351,9 +350,7 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
{!!booking?.eventType?.price && !booking.paid && (
|
{!!booking?.eventType?.price && !booking.paid && (
|
||||||
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">Pending payment</Tag>
|
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">Pending payment</Tag>
|
||||||
)}
|
)}
|
||||||
{!booking.confirmed && !booking.rejected && (
|
{isPending && <Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">{t("unconfirmed")}</Tag>}
|
||||||
<Tag className="hidden ltr:ml-2 rtl:mr-2 sm:inline-flex">{t("unconfirmed")}</Tag>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{booking.description && (
|
{booking.description && (
|
||||||
<div
|
<div
|
||||||
|
@ -382,13 +379,9 @@ function BookingListItem(booking: BookingItemProps) {
|
||||||
<td className="whitespace-nowrap py-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4">
|
<td className="whitespace-nowrap py-4 text-right text-sm font-medium ltr:pr-4 rtl:pl-4">
|
||||||
{isUpcoming && !isCancelled ? (
|
{isUpcoming && !isCancelled ? (
|
||||||
<>
|
<>
|
||||||
{!booking.confirmed && !booking.rejected && user!.id === booking.user!.id && (
|
{isPending && user?.id === booking.user?.id && <TableActions actions={pendingActions} />}
|
||||||
<TableActions actions={pendingActions} />
|
{isConfirmed && <TableActions actions={bookedActions} />}
|
||||||
)}
|
{isRejected && <div className="text-sm text-gray-500">{t("rejected")}</div>}
|
||||||
{booking.confirmed && !booking.rejected && <TableActions actions={bookedActions} />}
|
|
||||||
{!booking.confirmed && booking.rejected && (
|
|
||||||
<div className="text-sm text-gray-500">{t("rejected")}</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{isCancelled && booking.rescheduled && (
|
{isCancelled && booking.rescheduled && (
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import { ClockIcon, CreditCardIcon, RefreshIcon, UserIcon, UsersIcon } from "@heroicons/react/solid";
|
import {
|
||||||
import { SchedulingType } from "@prisma/client";
|
ClipboardCheckIcon,
|
||||||
import { Prisma } from "@prisma/client";
|
ClockIcon,
|
||||||
import React, { useMemo } from "react";
|
CreditCardIcon,
|
||||||
|
RefreshIcon,
|
||||||
|
UserIcon,
|
||||||
|
UsersIcon,
|
||||||
|
} from "@heroicons/react/solid";
|
||||||
|
import { Prisma, SchedulingType } from "@prisma/client";
|
||||||
|
import { useMemo } from "react";
|
||||||
import { FormattedNumber, IntlProvider } from "react-intl";
|
import { FormattedNumber, IntlProvider } from "react-intl";
|
||||||
|
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import classNames from "@lib/classNames";
|
import classNames from "@lib/classNames";
|
||||||
|
|
||||||
const eventTypeData = Prisma.validator<Prisma.EventTypeArgs>()({
|
const eventTypeData = Prisma.validator<Prisma.EventTypeArgs>()({
|
||||||
select: {
|
select: baseEventTypeSelect,
|
||||||
id: true,
|
|
||||||
length: true,
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
schedulingType: true,
|
|
||||||
recurringEvent: true,
|
|
||||||
description: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type EventType = Prisma.EventTypeGetPayload<typeof eventTypeData>;
|
type EventType = Prisma.EventTypeGetPayload<typeof eventTypeData>;
|
||||||
|
@ -83,6 +82,12 @@ export const EventTypeDescription = ({ eventType, className }: EventTypeDescript
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
{eventType.requiresConfirmation && (
|
||||||
|
<li className="mr-4 flex items-center whitespace-nowrap">
|
||||||
|
<ClipboardCheckIcon className="mr-1.5 inline h-4 w-4 text-neutral-400" aria-hidden="true" />
|
||||||
|
Opt-in
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { BookingStatus, Prisma } from "@prisma/client";
|
||||||
import { buffer } from "micro";
|
import { buffer } from "micro";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
|
@ -44,13 +44,13 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
...bookingMinimalSelect,
|
...bookingMinimalSelect,
|
||||||
confirmed: true,
|
|
||||||
location: true,
|
location: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
uid: true,
|
uid: true,
|
||||||
paid: true,
|
paid: true,
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
|
status: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -125,10 +125,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
|
||||||
|
|
||||||
const bookingData: Prisma.BookingUpdateInput = {
|
const bookingData: Prisma.BookingUpdateInput = {
|
||||||
paid: true,
|
paid: true,
|
||||||
confirmed: true,
|
status: BookingStatus.ACCEPTED,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (booking.confirmed) {
|
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
|
||||||
|
if (isConfirmed) {
|
||||||
const eventManager = new EventManager(user);
|
const eventManager = new EventManager(user);
|
||||||
const scheduleResult = await eventManager.create(evt);
|
const scheduleResult = await eventManager.create(evt);
|
||||||
bookingData.references = { create: scheduleResult.referencesToCreate };
|
bookingData.references = { create: scheduleResult.referencesToCreate };
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Credential, SelectedCalendar } from "@prisma/client";
|
import { BookingStatus, Credential, SelectedCalendar } from "@prisma/client";
|
||||||
|
|
||||||
import { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
|
import { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
|
||||||
import { getBusyVideoTimes } from "@calcom/core/videoClient";
|
import { getBusyVideoTimes } from "@calcom/core/videoClient";
|
||||||
|
@ -19,24 +19,13 @@ async function getBusyTimes(params: {
|
||||||
const busyTimes: EventBusyDate[] = await prisma.booking
|
const busyTimes: EventBusyDate[] = await prisma.booking
|
||||||
.findMany({
|
.findMany({
|
||||||
where: {
|
where: {
|
||||||
AND: [
|
|
||||||
{
|
|
||||||
userId,
|
userId,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
startTime: { gte: new Date(startTime) },
|
startTime: { gte: new Date(startTime) },
|
||||||
endTime: { lte: new Date(endTime) },
|
endTime: { lte: new Date(endTime) },
|
||||||
|
status: {
|
||||||
|
in: [BookingStatus.ACCEPTED],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
status: "ACCEPTED",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: "PENDING",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
startTime: true,
|
startTime: true,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Prisma, UserPlan } from "@prisma/client";
|
import { Prisma, UserPlan } from "@prisma/client";
|
||||||
|
|
||||||
|
import { baseEventTypeSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
import prisma from "@lib/prisma";
|
import prisma from "@lib/prisma";
|
||||||
|
|
||||||
type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (...args: any) => Promise<infer R>
|
type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (...args: any) => Promise<infer R>
|
||||||
|
@ -37,18 +39,10 @@ export async function getTeamWithMembers(id?: number, slug?: string) {
|
||||||
hidden: false,
|
hidden: false,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
length: true,
|
|
||||||
slug: true,
|
|
||||||
schedulingType: true,
|
|
||||||
recurringEvent: true,
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
users: {
|
users: {
|
||||||
select: userSelect,
|
select: userSelect,
|
||||||
},
|
},
|
||||||
|
...baseEventTypeSelect,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { Attendee, Booking } from "@prisma/client";
|
import { Attendee, Booking } from "@prisma/client";
|
||||||
|
|
||||||
export type BookingConfirmBody = {
|
|
||||||
confirmed: boolean;
|
|
||||||
id: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BookingCreateBody = {
|
export type BookingCreateBody = {
|
||||||
email: string;
|
email: string;
|
||||||
end: string;
|
end: string;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
type Handlers = {
|
||||||
|
[method in "GET" | "POST" | "PATCH" | "PUT" | "DELETE"]?: Promise<{ default: NextApiHandler }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Allows us to split big API handlers by method and auto catch unsupported methods */
|
||||||
|
const defaultHandler = (handlers: Handlers) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const handler = (await handlers[req.method as keyof typeof handlers])?.default;
|
||||||
|
|
||||||
|
if (!handler) return res.status(405).json({ message: "Method not allowed" });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handler(req, res);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return res.status(500).json({ message: "Something went wrong" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaultHandler;
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
|
||||||
|
type Handle<T> = (req: NextApiRequest, res: NextApiResponse) => Promise<T>;
|
||||||
|
|
||||||
|
/** Allows us to get type inference from API handler responses */
|
||||||
|
function defaultResponder<T>(f: Handle<T>) {
|
||||||
|
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
try {
|
||||||
|
const result = await f(req, res);
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
res.statusCode = err.statusCode;
|
||||||
|
res.json({ message: err.message });
|
||||||
|
} else if (err instanceof Stripe.errors.StripeInvalidRequestError) {
|
||||||
|
console.error("err", err);
|
||||||
|
res.statusCode = err.statusCode || 500;
|
||||||
|
res.json({ message: "Stripe error: " + err.message });
|
||||||
|
} else if (err instanceof ZodError && err.name === "ZodError") {
|
||||||
|
console.error("err", JSON.parse(err.message)[0].message);
|
||||||
|
res.statusCode = 400;
|
||||||
|
res.json({
|
||||||
|
message: "Validation errors: " + err.issues.map((i) => `—'${i.path}' ${i.message}`).join(". "),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("err", err);
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.json({ message: "Unknown error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultResponder;
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as defaultHandler } from "./defaultHandler";
|
||||||
|
export { default as defaultResponder } from "./defaultResponder";
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./api";
|
|
@ -23,6 +23,7 @@ import defaultEvents, {
|
||||||
getUsernameSlugLink,
|
getUsernameSlugLink,
|
||||||
} from "@calcom/lib/defaultEvents";
|
} from "@calcom/lib/defaultEvents";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
|
|
||||||
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
|
import { useExposePlanGlobally } from "@lib/hooks/useExposePlanGlobally";
|
||||||
import useTheme from "@lib/hooks/useTheme";
|
import useTheme from "@lib/hooks/useTheme";
|
||||||
|
@ -274,17 +275,8 @@ const getEventTypesWithHiddenFromDB = async (userId: number, plan: UserPlan) =>
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
title: true,
|
|
||||||
length: true,
|
|
||||||
description: true,
|
|
||||||
hidden: true,
|
|
||||||
schedulingType: true,
|
|
||||||
recurringEvent: true,
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
metadata: true,
|
metadata: true,
|
||||||
|
...baseEventTypeSelect,
|
||||||
},
|
},
|
||||||
take: plan === UserPlan.FREE ? 1 : undefined,
|
take: plan === UserPlan.FREE ? 1 : undefined,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
import { Booking, BookingStatus, Prisma, SchedulingType, User } from "@prisma/client";
|
import { Booking, BookingStatus, Prisma, SchedulingType, User } from "@prisma/client";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import EventManager from "@calcom/core/EventManager";
|
import EventManager from "@calcom/core/EventManager";
|
||||||
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
import { isPrismaObjOrUndefined } from "@calcom/lib";
|
||||||
import logger from "@calcom/lib/logger";
|
import logger from "@calcom/lib/logger";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
import type { AdditionInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
|
||||||
import { refund } from "@ee/lib/stripe/server";
|
import { refund } from "@ee/lib/stripe/server";
|
||||||
|
|
||||||
import { asStringOrNull } from "@lib/asStringOrNull";
|
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
|
import { HttpError } from "@lib/core/http/error";
|
||||||
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
|
import { sendDeclinedEmails, sendScheduledEmails } from "@lib/emails/email-manager";
|
||||||
import prisma from "@lib/prisma";
|
|
||||||
import { BookingConfirmBody } from "@lib/types/booking";
|
|
||||||
|
|
||||||
import { getTranslation } from "@server/lib/i18n";
|
import { getTranslation } from "@server/lib/i18n";
|
||||||
|
|
||||||
|
import { defaultHandler, defaultResponder } from "~/common";
|
||||||
|
|
||||||
const authorized = async (
|
const authorized = async (
|
||||||
currentUser: Pick<User, "id">,
|
currentUser: Pick<User, "id">,
|
||||||
booking: Pick<Booking, "eventTypeId" | "userId">
|
booking: Pick<Booking, "eventTypeId" | "userId">
|
||||||
|
@ -43,20 +45,30 @@ const authorized = async (
|
||||||
|
|
||||||
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
|
const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
const bookingConfirmPatchBodySchema = z.object({
|
||||||
const session = await getSession({ req: req });
|
confirmed: z.boolean(),
|
||||||
|
id: z.number(),
|
||||||
|
recurringEventId: z.string().optional(),
|
||||||
|
reason: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function patchHandler(req: NextApiRequest) {
|
||||||
|
const session = await getSession({ req });
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
return res.status(401).json({ message: "Not authenticated" });
|
throw new HttpError({ statusCode: 401, message: "Not authenticated" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const reqBody = req.body as BookingConfirmBody;
|
const {
|
||||||
const bookingId = reqBody.id;
|
id: bookingId,
|
||||||
|
recurringEventId,
|
||||||
if (!bookingId) {
|
reason: rejectionReason,
|
||||||
return res.status(400).json({ message: "bookingId missing" });
|
confirmed,
|
||||||
}
|
} = bookingConfirmPatchBodySchema.parse(req.body);
|
||||||
|
|
||||||
const currentUser = await prisma.user.findFirst({
|
const currentUser = await prisma.user.findFirst({
|
||||||
|
rejectOnNotFound() {
|
||||||
|
throw new HttpError({ statusCode: 404, message: "User not found" });
|
||||||
|
},
|
||||||
where: {
|
where: {
|
||||||
id: session.user.id,
|
id: session.user.id,
|
||||||
},
|
},
|
||||||
|
@ -74,24 +86,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!currentUser) {
|
|
||||||
return res.status(404).json({ message: "User not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tOrganizer = await getTranslation(currentUser.locale ?? "en", "common");
|
const tOrganizer = await getTranslation(currentUser.locale ?? "en", "common");
|
||||||
|
|
||||||
if (req.method === "PATCH") {
|
|
||||||
const booking = await prisma.booking.findFirst({
|
const booking = await prisma.booking.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
},
|
},
|
||||||
|
rejectOnNotFound() {
|
||||||
|
throw new HttpError({ statusCode: 404, message: "Booking not found" });
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
title: true,
|
title: true,
|
||||||
description: true,
|
description: true,
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
endTime: true,
|
endTime: true,
|
||||||
confirmed: true,
|
|
||||||
attendees: true,
|
attendees: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
eventType: {
|
eventType: {
|
||||||
|
@ -107,19 +116,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
destinationCalendar: true,
|
destinationCalendar: true,
|
||||||
paid: true,
|
paid: true,
|
||||||
recurringEventId: true,
|
recurringEventId: true,
|
||||||
|
status: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!booking) {
|
|
||||||
return res.status(404).json({ message: "booking not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await authorized(currentUser, booking))) {
|
if (!(await authorized(currentUser, booking))) {
|
||||||
return res.status(401).end();
|
throw new HttpError({ statusCode: 401, message: "UNAUTHORIZED" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (booking.confirmed) {
|
const isConfirmed = booking.status === BookingStatus.ACCEPTED;
|
||||||
return res.status(400).json({ message: "booking already confirmed" });
|
if (isConfirmed) {
|
||||||
|
throw new HttpError({ statusCode: 400, message: "booking already confirmed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** When a booking that requires payment its being confirmed but doesn't have any payment,
|
/** When a booking that requires payment its being confirmed but doesn't have any payment,
|
||||||
|
@ -131,11 +138,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
confirmed: true,
|
status: BookingStatus.ACCEPTED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(204).end();
|
req.statusCode = 204;
|
||||||
|
return { message: "Booking confirmed" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
const attendeesListPromises = booking.attendees.map(async (attendee) => {
|
||||||
|
@ -173,7 +181,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
|
|
||||||
const recurringEvent = booking.eventType?.recurringEvent as RecurringEvent;
|
const recurringEvent = booking.eventType?.recurringEvent as RecurringEvent;
|
||||||
|
|
||||||
if (req.body.recurringEventId && recurringEvent) {
|
if (recurringEventId && recurringEvent) {
|
||||||
const groupedRecurringBookings = await prisma.booking.groupBy({
|
const groupedRecurringBookings = await prisma.booking.groupBy({
|
||||||
where: {
|
where: {
|
||||||
recurringEventId: booking.recurringEventId,
|
recurringEventId: booking.recurringEventId,
|
||||||
|
@ -186,7 +194,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
recurringEvent.count = groupedRecurringBookings[0]._count;
|
recurringEvent.count = groupedRecurringBookings[0]._count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reqBody.confirmed) {
|
if (confirmed) {
|
||||||
const eventManager = new EventManager(currentUser);
|
const eventManager = new EventManager(currentUser);
|
||||||
const scheduleResult = await eventManager.create(evt);
|
const scheduleResult = await eventManager.create(evt);
|
||||||
|
|
||||||
|
@ -211,20 +219,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
try {
|
try {
|
||||||
await sendScheduledEmails(
|
await sendScheduledEmails(
|
||||||
{ ...evt, additionInformation: metadata },
|
{ ...evt, additionInformation: metadata },
|
||||||
req.body.recurringEventId ? recurringEvent : {} // Send email with recurring event info only on recurring event context
|
recurringEventId ? recurringEvent : {} // Send email with recurring event info only on recurring event context
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.recurringEventId) {
|
if (recurringEventId) {
|
||||||
// The booking to confirm is a recurring event and comes from /booking/upcoming, proceeding to mark all related
|
// The booking to confirm is a recurring event and comes from /booking/upcoming, proceeding to mark all related
|
||||||
// bookings as confirmed. Prisma updateMany does not support relations, so doing this in two steps for now.
|
// bookings as confirmed. Prisma updateMany does not support relations, so doing this in two steps for now.
|
||||||
const unconfirmedRecurringBookings = await prisma.booking.findMany({
|
const unconfirmedRecurringBookings = await prisma.booking.findMany({
|
||||||
where: {
|
where: {
|
||||||
recurringEventId: req.body.recurringEventId,
|
recurringEventId,
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
unconfirmedRecurringBookings.map(async (recurringBooking) => {
|
unconfirmedRecurringBookings.map(async (recurringBooking) => {
|
||||||
|
@ -233,7 +241,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: recurringBooking.id,
|
id: recurringBooking.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
confirmed: true,
|
status: BookingStatus.ACCEPTED,
|
||||||
references: {
|
references: {
|
||||||
create: scheduleResult.referencesToCreate,
|
create: scheduleResult.referencesToCreate,
|
||||||
},
|
},
|
||||||
|
@ -248,25 +256,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
confirmed: true,
|
status: BookingStatus.ACCEPTED,
|
||||||
references: {
|
references: {
|
||||||
create: scheduleResult.referencesToCreate,
|
create: scheduleResult.referencesToCreate,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(204).end();
|
|
||||||
} else {
|
} else {
|
||||||
const rejectionReason = asStringOrNull(req.body.reason) || "";
|
|
||||||
evt.rejectionReason = rejectionReason;
|
evt.rejectionReason = rejectionReason;
|
||||||
if (req.body.recurringEventId) {
|
if (recurringEventId) {
|
||||||
// The booking to reject is a recurring event and comes from /booking/upcoming, proceeding to mark all related
|
// The booking to reject is a recurring event and comes from /booking/upcoming, proceeding to mark all related
|
||||||
// bookings as rejected. Prisma updateMany does not support relations, so doing this in two steps for now.
|
// bookings as rejected. Prisma updateMany does not support relations, so doing this in two steps for now.
|
||||||
const unconfirmedRecurringBookings = await prisma.booking.findMany({
|
const unconfirmedRecurringBookings = await prisma.booking.findMany({
|
||||||
where: {
|
where: {
|
||||||
recurringEventId: req.body.recurringEventId,
|
recurringEventId,
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
unconfirmedRecurringBookings.map(async (recurringBooking) => {
|
unconfirmedRecurringBookings.map(async (recurringBooking) => {
|
||||||
|
@ -275,9 +280,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: recurringBooking.id,
|
id: recurringBooking.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
rejected: true,
|
|
||||||
status: BookingStatus.REJECTED,
|
status: BookingStatus.REJECTED,
|
||||||
rejectionReason: rejectionReason,
|
rejectionReason,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -288,16 +292,22 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: bookingId,
|
id: bookingId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
rejected: true,
|
|
||||||
status: BookingStatus.REJECTED,
|
status: BookingStatus.REJECTED,
|
||||||
rejectionReason: rejectionReason,
|
rejectionReason,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendDeclinedEmails(evt, req.body.recurringEventId ? recurringEvent : {}); // Send email with recurring event info only on recurring event context
|
await sendDeclinedEmails(evt, recurringEventId ? recurringEvent : {}); // Send email with recurring event info only on recurring event context
|
||||||
|
}
|
||||||
|
|
||||||
res.status(204).end();
|
req.statusCode = 204;
|
||||||
}
|
return { message: "Booking " + confirmed ? "confirmed" : "rejected" };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BookConfirmPatchResponse = Awaited<ReturnType<typeof patchHandler>>;
|
||||||
|
|
||||||
|
export default defaultHandler({
|
||||||
|
// To prevent too much git diff until moved to another file
|
||||||
|
PATCH: Promise.resolve({ default: defaultResponder(patchHandler) }),
|
||||||
|
});
|
||||||
|
|
|
@ -478,6 +478,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const dynamicEventSlugRef = !eventTypeId ? eventTypeSlug : null;
|
const dynamicEventSlugRef = !eventTypeId ? eventTypeSlug : null;
|
||||||
const dynamicGroupSlugRef = !eventTypeId ? (reqBody.user as string).toLowerCase() : null;
|
const dynamicGroupSlugRef = !eventTypeId ? (reqBody.user as string).toLowerCase() : null;
|
||||||
|
|
||||||
|
const isConfirmedByDefault = (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid;
|
||||||
const newBookingData: Prisma.BookingCreateInput = {
|
const newBookingData: Prisma.BookingCreateInput = {
|
||||||
uid,
|
uid,
|
||||||
title: evt.title,
|
title: evt.title,
|
||||||
|
@ -485,7 +486,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
endTime: dayjs(evt.endTime).toDate(),
|
endTime: dayjs(evt.endTime).toDate(),
|
||||||
description: evt.additionalNotes,
|
description: evt.additionalNotes,
|
||||||
customInputs: isPrismaObjOrUndefined(evt.customInputs),
|
customInputs: isPrismaObjOrUndefined(evt.customInputs),
|
||||||
confirmed: (!eventType.requiresConfirmation && !eventType.price) || !!rescheduleUid,
|
status: isConfirmedByDefault ? BookingStatus.ACCEPTED : BookingStatus.PENDING,
|
||||||
location: evt.location,
|
location: evt.location,
|
||||||
eventType: eventTypeRel,
|
eventType: eventTypeRel,
|
||||||
attendees: {
|
attendees: {
|
||||||
|
|
|
@ -200,7 +200,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
id: bookingToDelete.id,
|
id: bookingToDelete.id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
rejected: true,
|
status: BookingStatus.REJECTED,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReminderType } from "@prisma/client";
|
import { BookingStatus, ReminderType } from "@prisma/client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
@ -26,8 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
for (const interval of reminderIntervalMinutes) {
|
for (const interval of reminderIntervalMinutes) {
|
||||||
const bookings = await prisma.booking.findMany({
|
const bookings = await prisma.booking.findMany({
|
||||||
where: {
|
where: {
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
rejected: false,
|
|
||||||
createdAt: {
|
createdAt: {
|
||||||
lte: dayjs().add(-interval, "minutes").toDate(),
|
lte: dayjs().add(-interval, "minutes").toDate(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Prisma } from "@prisma/client";
|
import { BookingStatus, Prisma } from "@prisma/client";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
|
@ -105,7 +105,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
status: "CANCELLED",
|
status: BookingStatus.CANCELLED,
|
||||||
rejectionReason: "Payment provider got removed",
|
rejectionReason: "Payment provider got removed",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -113,8 +113,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const bookingReferences = await prisma.booking
|
const bookingReferences = await prisma.booking
|
||||||
.findMany({
|
.findMany({
|
||||||
where: {
|
where: {
|
||||||
confirmed: true,
|
status: BookingStatus.ACCEPTED,
|
||||||
rejected: false,
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
|
|
@ -21,12 +21,7 @@ export const createBookingsFixture = (page: Page) => {
|
||||||
userId: number,
|
userId: number,
|
||||||
username: string | null,
|
username: string | null,
|
||||||
eventTypeId = -1,
|
eventTypeId = -1,
|
||||||
{
|
{ rescheduled = false, paid = false, status = "ACCEPTED" }: Partial<Prisma.BookingCreateInput> = {}
|
||||||
confirmed = true,
|
|
||||||
rescheduled = false,
|
|
||||||
paid = false,
|
|
||||||
status = "ACCEPTED",
|
|
||||||
}: Partial<Prisma.BookingCreateInput> = {}
|
|
||||||
) => {
|
) => {
|
||||||
const startDate = dayjs().add(1, "day").toDate();
|
const startDate = dayjs().add(1, "day").toDate();
|
||||||
const seed = `${username}:${dayjs(startDate).utc().format()}:${new Date().getTime()}`;
|
const seed = `${username}:${dayjs(startDate).utc().format()}:${new Date().getTime()}`;
|
||||||
|
@ -54,7 +49,6 @@ export const createBookingsFixture = (page: Page) => {
|
||||||
id: eventTypeId,
|
id: eventTypeId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
confirmed,
|
|
||||||
rescheduled,
|
rescheduled,
|
||||||
paid,
|
paid,
|
||||||
status,
|
status,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { z } from "zod";
|
||||||
import getApps, { getLocationOptions } from "@calcom/app-store/utils";
|
import getApps, { getLocationOptions } from "@calcom/app-store/utils";
|
||||||
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
|
||||||
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
|
||||||
import { bookingMinimalSelect } from "@calcom/prisma";
|
import { baseEventTypeSelect, bookingMinimalSelect } from "@calcom/prisma";
|
||||||
import { RecurringEvent } from "@calcom/types/Calendar";
|
import { RecurringEvent } from "@calcom/types/Calendar";
|
||||||
|
|
||||||
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
|
||||||
|
@ -131,16 +131,6 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
async resolve({ ctx }) {
|
async resolve({ ctx }) {
|
||||||
const { prisma } = ctx;
|
const { prisma } = ctx;
|
||||||
const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
length: true,
|
|
||||||
schedulingType: true,
|
|
||||||
recurringEvent: true,
|
|
||||||
slug: true,
|
|
||||||
hidden: true,
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
position: true,
|
position: true,
|
||||||
successRedirectUrl: true,
|
successRedirectUrl: true,
|
||||||
hashedLink: true,
|
hashedLink: true,
|
||||||
|
@ -151,6 +141,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
name: true,
|
name: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...baseEventTypeSelect,
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
|
@ -328,7 +319,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
// handled separately for each occurrence
|
// handled separately for each occurrence
|
||||||
OR: [
|
OR: [
|
||||||
{
|
{
|
||||||
AND: [{ NOT: { recurringEventId: { equals: null } } }, { confirmed: false }],
|
AND: [{ NOT: { recurringEventId: { equals: null } } }, { status: BookingStatus.PENDING }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
AND: [
|
AND: [
|
||||||
|
@ -398,8 +389,6 @@ const loggedInViewerRouter = createProtectedRouter()
|
||||||
select: {
|
select: {
|
||||||
...bookingMinimalSelect,
|
...bookingMinimalSelect,
|
||||||
uid: true,
|
uid: true,
|
||||||
confirmed: true,
|
|
||||||
rejected: true,
|
|
||||||
recurringEventId: true,
|
recurringEventId: true,
|
||||||
location: true,
|
location: true,
|
||||||
eventType: {
|
eventType: {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"~/*": ["modules/*"],
|
||||||
"@components/*": ["components/*"],
|
"@components/*": ["components/*"],
|
||||||
"@lib/*": ["lib/*"],
|
"@lib/*": ["lib/*"],
|
||||||
"@server/*": ["server/*"],
|
"@server/*": ["server/*"],
|
||||||
|
|
|
@ -14,7 +14,6 @@ import type {
|
||||||
IntegrationCalendar,
|
IntegrationCalendar,
|
||||||
NewCalendarEventType,
|
NewCalendarEventType,
|
||||||
} from "@calcom/types/Calendar";
|
} from "@calcom/types/Calendar";
|
||||||
import type { PartialReference } from "@calcom/types/EventManager";
|
|
||||||
|
|
||||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||||
|
|
||||||
|
@ -103,11 +102,8 @@ export default class GoogleCalendarService implements Calendar {
|
||||||
timeZone: calEventRaw.organizer.timeZone,
|
timeZone: calEventRaw.organizer.timeZone,
|
||||||
},
|
},
|
||||||
attendees: [
|
attendees: [
|
||||||
{ ...calEventRaw.organizer, organizer: true },
|
{ ...calEventRaw.organizer, organizer: true, responseStatus: "accepted" },
|
||||||
...calEventRaw.attendees.map((attendee) => ({
|
...calEventRaw.attendees.map((attendee) => ({ ...attendee, responseStatus: "accepted" })),
|
||||||
...attendee,
|
|
||||||
responseStatus: "accepted",
|
|
||||||
})),
|
|
||||||
],
|
],
|
||||||
reminders: {
|
reminders: {
|
||||||
useDefault: true,
|
useDefault: true,
|
||||||
|
@ -185,7 +181,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||||
dateTime: event.endTime,
|
dateTime: event.endTime,
|
||||||
timeZone: event.organizer.timeZone,
|
timeZone: event.organizer.timeZone,
|
||||||
},
|
},
|
||||||
attendees: event.attendees,
|
attendees: [{ ...event.organizer, organizer: true, responseStatus: "accepted" }, ...event.attendees],
|
||||||
reminders: {
|
reminders: {
|
||||||
useDefault: true,
|
useDefault: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- Set BookingStatus.PENDING
|
||||||
|
UPDATE "Booking" SET "status" = 'pending' WHERE "confirmed" = false AND "rejected" = false AND "rescheduled" IS NOT true;
|
||||||
|
|
||||||
|
-- Set BookingStatus.REJECTED
|
||||||
|
UPDATE "Booking" SET "status" = 'rejected' WHERE "confirmed" = false AND "rejected" = true AND "rescheduled" IS NOT true;
|
||||||
|
|
||||||
|
-- Set BookingStatus.CANCELLED
|
||||||
|
UPDATE "Booking" SET "status" = 'cancelled' WHERE "confirmed" = false AND "rejected" = false AND "rescheduled" IS true;
|
||||||
|
|
||||||
|
-- Set BookingStatus.ACCEPTED
|
||||||
|
UPDATE "Booking" SET "status" = 'accepted' WHERE "confirmed" = true AND "rejected" = false AND "rescheduled" IS NOT true;
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `confirmed` on the `Booking` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `rejected` on the `Booking` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Booking" DROP COLUMN "confirmed",
|
||||||
|
DROP COLUMN "rejected";
|
|
@ -273,8 +273,6 @@ model Booking {
|
||||||
dailyRef DailyEventReference?
|
dailyRef DailyEventReference?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime?
|
updatedAt DateTime?
|
||||||
confirmed Boolean @default(true)
|
|
||||||
rejected Boolean @default(false)
|
|
||||||
status BookingStatus @default(ACCEPTED)
|
status BookingStatus @default(ACCEPTED)
|
||||||
paid Boolean @default(false)
|
paid Boolean @default(false)
|
||||||
payment Payment[]
|
payment Payment[]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
import { BookingStatus, MembershipRole, Prisma, UserPlan } from "@prisma/client";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { uuid } from "short-uuid";
|
import { uuid } from "short-uuid";
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ async function createUserAndEventType(opts: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
confirmed: bookingInput.confirmed,
|
status: bookingInput.status,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -238,7 +238,7 @@ async function main() {
|
||||||
title: "30min",
|
title: "30min",
|
||||||
startTime: dayjs().add(2, "day").toDate(),
|
startTime: dayjs().add(2, "day").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(30, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -289,7 +289,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").toDate(),
|
startTime: dayjs().add(1, "day").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -297,7 +297,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").add(1, "week").toDate(),
|
startTime: dayjs().add(1, "day").add(1, "week").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(1, "week").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(1, "week").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -305,7 +305,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").add(2, "week").toDate(),
|
startTime: dayjs().add(1, "day").add(2, "week").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(2, "week").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(2, "week").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -313,7 +313,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").add(3, "week").toDate(),
|
startTime: dayjs().add(1, "day").add(3, "week").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(3, "week").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(3, "week").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -321,7 +321,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").add(4, "week").toDate(),
|
startTime: dayjs().add(1, "day").add(4, "week").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(4, "week").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(4, "week").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -329,7 +329,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
recurringEventId: Buffer.from("yoga-class").toString("base64"),
|
||||||
startTime: dayjs().add(1, "day").add(5, "week").toDate(),
|
startTime: dayjs().add(1, "day").add(5, "week").toDate(),
|
||||||
endTime: dayjs().add(1, "day").add(5, "week").add(30, "minutes").toDate(),
|
endTime: dayjs().add(1, "day").add(5, "week").add(30, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -346,7 +346,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
||||||
startTime: dayjs().add(2, "day").toDate(),
|
startTime: dayjs().add(2, "day").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(60, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(60, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -354,7 +354,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
||||||
startTime: dayjs().add(2, "day").add(2, "week").toDate(),
|
startTime: dayjs().add(2, "day").add(2, "week").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(2, "week").add(60, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(2, "week").add(60, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -362,7 +362,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
||||||
startTime: dayjs().add(2, "day").add(4, "week").toDate(),
|
startTime: dayjs().add(2, "day").add(4, "week").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(4, "week").add(60, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(4, "week").add(60, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -370,7 +370,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
||||||
startTime: dayjs().add(2, "day").add(8, "week").toDate(),
|
startTime: dayjs().add(2, "day").add(8, "week").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(8, "week").add(60, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(8, "week").add(60, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
|
@ -378,7 +378,7 @@ async function main() {
|
||||||
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
recurringEventId: Buffer.from("tennis-class").toString("base64"),
|
||||||
startTime: dayjs().add(2, "day").add(10, "week").toDate(),
|
startTime: dayjs().add(2, "day").add(10, "week").toDate(),
|
||||||
endTime: dayjs().add(2, "day").add(10, "week").add(60, "minutes").toDate(),
|
endTime: dayjs().add(2, "day").add(10, "week").add(60, "minutes").toDate(),
|
||||||
confirmed: false,
|
status: BookingStatus.PENDING,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
export const baseEventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
description: true,
|
||||||
|
length: true,
|
||||||
|
schedulingType: true,
|
||||||
|
recurringEvent: true,
|
||||||
|
slug: true,
|
||||||
|
hidden: true,
|
||||||
|
price: true,
|
||||||
|
currency: true,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
});
|
|
@ -1 +1,2 @@
|
||||||
export * from "./booking";
|
export * from "./booking";
|
||||||
|
export * from "./event-types";
|
||||||
|
|
Loading…
Reference in New Issue
Block a user