chore: refactor apiRouteMiddleware and add it to some api routes in App Router (#19146)

* chore: remove try and catch from api routes in app router

* refactor

* refactor apiRouteMiddleware

* refactor
This commit is contained in:
Benny Joo authored and GitHub committed 2025-02-07 11:04:34 +00:00
1 parent 24a080b988
commit e79af63e6c
6 files changed
+127 -156

No files matched your search

+53 -72
View File
@@ -37,87 +37,68 @@ async function authMiddleware() {
}
// TODO: It doesn't seem to be used from within the app. It is possible that someone outside Cal.com is using this GET endpoint
export async function GET() {
try {
const user = await authMiddleware();
async function getHandler() {
const user = await authMiddleware();
const selectedCalendarIds = await SelectedCalendarRepository.findMany({
where: { userId: user.id },
select: { externalId: true },
});
// get user's credentials + their connected integrations
const calendarCredentials = getCalendarCredentials(user.credentials);
// get all the connected integrations' calendars (from third party)
const { connectedCalendars } = await getConnectedCalendars(
calendarCredentials,
user.userLevelSelectedCalendars
);
const selectedCalendarIds = await SelectedCalendarRepository.findMany({
where: { userId: user.id },
select: { externalId: true },
});
// get user's credentials + their connected integrations
const calendarCredentials = getCalendarCredentials(user.credentials);
// get all the connected integrations' calendars (from third party)
const { connectedCalendars } = await getConnectedCalendars(
calendarCredentials,
user.userLevelSelectedCalendars
);
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
const selectableCalendars = calendars.map((cal) => {
return { selected: selectedCalendarIds.findIndex((s) => s.externalId === cal.externalId) > -1, ...cal };
});
const calendars = connectedCalendars.flatMap((c) => c.calendars).filter(notEmpty);
const selectableCalendars = calendars.map((cal) => {
return { selected: selectedCalendarIds.findIndex((s) => s.externalId === cal.externalId) > -1, ...cal };
});
return NextResponse.json(selectableCalendars);
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
return NextResponse.json(selectableCalendars);
}
export async function POST(req: NextRequest) {
try {
const user = await authMiddleware();
const body = await req.json();
const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse(body);
async function postHandler(req: NextRequest) {
const user = await authMiddleware();
const body = await req.json();
const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse(body);
await SelectedCalendarRepository.upsert({
await SelectedCalendarRepository.upsert({
userId: user.id,
integration,
externalId,
credentialId,
eventTypeId: eventTypeId ?? null,
});
return NextResponse.json({ message: "Calendar Selection Saved" });
}
async function deleteHandler(req: NextRequest) {
const user = await authMiddleware();
const searchParams = Object.fromEntries(req.nextUrl.searchParams.entries());
const { integration, externalId, credentialId, eventTypeId } =
selectedCalendarSelectSchema.parse(searchParams);
const calendarCacheRepository = await CalendarCache.initFromCredentialId(credentialId);
await calendarCacheRepository.unwatchCalendar({
calendarId: externalId,
eventTypeIds: [eventTypeId ?? null],
});
await SelectedCalendarRepository.delete({
where: {
userId: user.id,
integration,
externalId,
credentialId,
integration,
eventTypeId: eventTypeId ?? null,
});
},
});
return NextResponse.json({ message: "Calendar Selection Saved" });
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
return NextResponse.json({ message: "Calendar Selection Saved" });
}
export async function DELETE(req: NextRequest) {
try {
const user = await authMiddleware();
const searchParams = Object.fromEntries(req.nextUrl.searchParams.entries());
const { integration, externalId, credentialId, eventTypeId } =
selectedCalendarSelectSchema.parse(searchParams);
const calendarCacheRepository = await CalendarCache.initFromCredentialId(credentialId);
await calendarCacheRepository.unwatchCalendar({
calendarId: externalId,
eventTypeIds: [eventTypeId ?? null],
});
await SelectedCalendarRepository.delete({
where: {
userId: user.id,
externalId,
integration,
eventTypeId: eventTypeId ?? null,
},
});
return NextResponse.json({ message: "Calendar Selection Saved" });
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}
export { deleteHandler as DELETE, postHandler as POST, getHandler as GET };
+4 -6
View File
@@ -4,6 +4,7 @@ import type { NextRequest } from "next/server";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
import { apiRouteMiddleware } from "@calcom/lib/server/apiRouteMiddleware";
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
@@ -25,10 +26,7 @@ async function handler(req: NextRequest) {
return NextResponse.json(result, { status: statusCode });
}
export async function DELETE(req: NextRequest) {
return handler(req);
}
const deleteHandler = apiRouteMiddleware((req: NextRequest) => handler(req));
const postHandler = apiRouteMiddleware((req: NextRequest) => handler(req));
export async function POST(req: NextRequest) {
return handler(req);
}
export { deleteHandler as DELETE, postHandler as POST };
+3 -2
View File
@@ -1,6 +1,7 @@
import { cardExamples } from "@pages/api/plain/example-cards";
import { createHmac } from "crypto";
import { headers } from "next/headers";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";
@@ -26,7 +27,7 @@ const inputSchema = z.object({
cardKeys: z.array(z.string()),
});
async function handler(request: Request) {
async function handler(request: NextRequest) {
const headersList = headers();
const requestBody = await request.json();
@@ -38,7 +39,7 @@ async function handler(request: Request) {
.digest("hex");
if (incomingSignature !== expectedSignature) {
return new Response("Forbidden", { status: 403 });
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
// Validate request body
+4 -3
View File
@@ -1,4 +1,5 @@
import { createHmac } from "crypto";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { z } from "zod";
@@ -14,16 +15,16 @@ const responseSchema = z.object({
chatAvatarUrl: z.string(),
});
async function handler(request: Request) {
async function handler(request: NextRequest) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const session = await getServerSession({ req: request as any });
if (!session?.user?.email) {
return new Response("Unauthorized - No session email found", { status: 401 });
return NextResponse.json({ error: "Unauthorized - No session email found" }, { status: 401 });
}
const secret = process.env.PLAIN_CHAT_HMAC_SECRET_KEY;
if (!secret) {
return new Response("Missing Plain Chat secret", { status: 500 });
return NextResponse.json({ error: "Missing Plain Chat secret" }, { status: 500 });
}
const hmac = createHmac("sha256", secret);
+62 -72
View File
@@ -22,82 +22,72 @@ const generateRandomString = () => {
return Math.random().toString(36).substring(2, 10);
};
export async function GET(req: NextRequest) {
try {
const searchParams = req.nextUrl.searchParams;
const { session_id } = querySchema.parse({
session_id: searchParams.get("session_id"),
async function getHandler(req: NextRequest) {
const searchParams = req.nextUrl.searchParams;
const { session_id } = querySchema.parse({
session_id: searchParams.get("session_id"),
});
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, {
expand: ["subscription"],
});
if (!checkoutSession) throw new HttpError({ statusCode: 404, message: "Checkout session not found" });
const subscription = checkoutSession.subscription as Stripe.Subscription;
if (checkoutSession.payment_status !== "paid")
throw new HttpError({ statusCode: 402, message: "Payment required" });
// Let's query to ensure that the team metadata carried over from the checkout session.
const parseCheckoutSessionMetadata = checkoutSessionMetadataSchema.safeParse(checkoutSession.metadata);
if (!parseCheckoutSessionMetadata.success) {
console.error(
"Team metadata not found in checkout session",
parseCheckoutSessionMetadata.error,
checkoutSession.id
);
}
if (!checkoutSession.metadata?.userId) {
throw new HttpError({
statusCode: 400,
message: "Can't publish team/org without userId",
});
}
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, {
expand: ["subscription"],
});
if (!checkoutSession) throw new HttpError({ statusCode: 404, message: "Checkout session not found" });
const checkoutSessionMetadata = parseCheckoutSessionMetadata.success
? parseCheckoutSessionMetadata.data
: {
teamName: checkoutSession?.metadata?.teamName ?? generateRandomString(),
teamSlug: checkoutSession?.metadata?.teamSlug ?? generateRandomString(),
userId: checkoutSession.metadata.userId,
};
const subscription = checkoutSession.subscription as Stripe.Subscription;
if (checkoutSession.payment_status !== "paid")
throw new HttpError({ statusCode: 402, message: "Payment required" });
// Let's query to ensure that the team metadata carried over from the checkout session.
const parseCheckoutSessionMetadata = checkoutSessionMetadataSchema.safeParse(checkoutSession.metadata);
if (!parseCheckoutSessionMetadata.success) {
console.error(
"Team metadata not found in checkout session",
parseCheckoutSessionMetadata.error,
checkoutSession.id
);
}
if (!checkoutSession.metadata?.userId) {
throw new HttpError({
statusCode: 400,
message: "Can't publish team/org without userId",
});
}
const checkoutSessionMetadata = parseCheckoutSessionMetadata.success
? parseCheckoutSessionMetadata.data
: {
teamName: checkoutSession?.metadata?.teamName ?? generateRandomString(),
teamSlug: checkoutSession?.metadata?.teamSlug ?? generateRandomString(),
userId: checkoutSession.metadata.userId,
};
const team = await prisma.team.create({
data: {
name: checkoutSessionMetadata.teamName,
slug: checkoutSessionMetadata.teamSlug,
members: {
create: {
userId: checkoutSessionMetadata.userId as number,
role: MembershipRole.OWNER,
accepted: true,
},
},
metadata: {
paymentId: checkoutSession.id,
subscriptionId: subscription.id || null,
subscriptionItemId: subscription.items.data[0].id || null,
const team = await prisma.team.create({
data: {
name: checkoutSessionMetadata.teamName,
slug: checkoutSessionMetadata.teamSlug,
members: {
create: {
userId: checkoutSessionMetadata.userId as number,
role: MembershipRole.OWNER,
accepted: true,
},
},
});
metadata: {
paymentId: checkoutSession.id,
subscriptionId: subscription.id || null,
subscriptionItemId: subscription.items.data[0].id || null,
},
},
});
// redirect to team screen
return NextResponse.redirect(
new URL(`/settings/teams/${team.id}/onboard-members?event=team_created`, req.nextUrl.origin),
{ status: 302 }
);
} catch (error) {
if (error instanceof HttpError) {
throw error;
}
if (error instanceof z.ZodError) {
return NextResponse.json({ message: "Invalid request data" }, { status: 400 });
}
return NextResponse.json({ message: "Internal server error" }, { status: 500 });
}
// redirect to team screen
return NextResponse.redirect(
new URL(`/settings/teams/${team.id}/onboard-members?event=team_created`, req.nextUrl.origin),
{ status: 302 }
);
}
export { getHandler as GET };
+1 -1
View File
@@ -6,7 +6,7 @@ import { NextResponse } from "next/server";
import { getServerErrorFromUnknown } from "./getServerErrorFromUnknown";
export const apiRouteMiddleware =
(handler: (req: Request, res?: Response) => Promise<Response>) =>
(handler: (req: NextRequest, res?: NextResponse) => Promise<NextResponse>) =>
async (req: NextRequest, res: NextResponse) => {
try {
return await handler(req, res);