From 87bc8d649afcea00ba8133160856e31a2564326e Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Tue, 11 Mar 2025 23:38:49 -0400 Subject: [PATCH] feat: add sentry report wrapper to every api route in appDir (#19966) * update oauth tests * fix parseRequestData * add logging * add fallback * use ?? * updates * use parseUrlFormData in saml/callback * update tests * fix * remove log * update * any -> unknown * use HttpErrors * addressed * unknown -> any * feat: Add entry report wrapper to every api route in appDir * fix * fix unit test * fix --- apps/web/app/api/auth/forgot-password/route.ts | 4 +--- apps/web/app/api/auth/oauth/me/route.ts | 5 +++-- .../web/app/api/auth/oauth/refreshToken/route.ts | 5 ++++- apps/web/app/api/auth/oauth/token/route.ts | 5 ++++- apps/web/app/api/auth/oidc/route.ts | 4 +--- apps/web/app/api/auth/reset-password/route.ts | 3 ++- apps/web/app/api/auth/saml/authorize/route.ts | 4 +--- apps/web/app/api/auth/saml/callback/route.ts | 4 +--- apps/web/app/api/auth/saml/token/route.ts | 4 +--- apps/web/app/api/auth/saml/userinfo/route.ts | 4 +--- apps/web/app/api/auth/setup/route.ts | 4 +--- .../api/auth/two-factor/totp/disable/route.ts | 5 ++++- .../app/api/auth/two-factor/totp/enable/route.ts | 5 ++++- .../app/api/auth/two-factor/totp/setup/route.ts | 5 ++++- apps/web/app/api/availability/calendar/route.ts | 5 ++++- apps/web/app/api/avatar/[uuid]/route.ts | 4 +--- apps/web/app/api/cancel/route.ts | 6 ++---- apps/web/app/api/cron/bookingReminder/route.ts | 5 ++++- .../app/api/cron/calendar-cache-cleanup/route.ts | 5 ++++- apps/web/app/api/cron/changeTimeZone/route.ts | 5 ++++- apps/web/app/api/cron/downgradeUsers/route.ts | 5 ++++- .../web/app/api/cron/monthlyDigestEmail/route.ts | 5 ++++- apps/web/app/api/cron/syncAppMeta/route.ts | 5 ++++- apps/web/app/api/cron/webhookTriggers/route.ts | 5 ++++- .../workflows/scheduleEmailReminders/route.ts | 4 +++- .../cron/workflows/scheduleSMSReminders/route.ts | 4 +++- .../workflows/scheduleWhatsappReminders/route.ts | 4 +++- apps/web/app/api/email/route.ts | 5 ++++- apps/web/app/api/future-opt-in/route.ts | 5 ++++- apps/web/app/api/generate-referral-link/route.ts | 5 ++++- apps/web/app/api/geolocation/route.ts | 5 ++++- apps/web/app/api/intercom-hash/route.ts | 5 ++++- apps/web/app/api/logo/route.ts | 5 ++++- apps/web/app/api/me/route.ts | 5 ++++- apps/web/app/api/nope/route.ts | 8 ++++++-- apps/web/app/api/recorded-daily-video/route.ts | 5 ++++- .../app/api/scim/v2.0/[...directory]/route.ts | 16 +++++++++++----- apps/web/app/api/sync/helpscout/route.ts | 5 ++++- apps/web/app/api/tasks/cleanup/route.ts | 6 +++++- apps/web/app/api/tasks/cron/route.ts | 6 +++++- apps/web/app/api/teams/[team]/upgrade/route.ts | 6 +++++- apps/web/app/api/teams/create/route.ts | 3 ++- .../app/api/teams/googleworkspace/add/route.ts | 5 ++++- .../api/teams/googleworkspace/callback/route.ts | 5 ++++- apps/web/app/api/username/route.ts | 5 ++++- apps/web/app/api/verify-booking-token/route.ts | 8 ++++++-- apps/web/app/api/version/route.ts | 5 ++++- apps/web/app/api/webhook/app-credential/route.ts | 5 ++++- .../tests/recorded-daily-video.test.ts | 10 ++++++++-- 49 files changed, 180 insertions(+), 76 deletions(-) diff --git a/apps/web/app/api/auth/forgot-password/route.ts b/apps/web/app/api/auth/forgot-password/route.ts index 249b6125a6..9e598196f6 100644 --- a/apps/web/app/api/auth/forgot-password/route.ts +++ b/apps/web/app/api/auth/forgot-password/route.ts @@ -46,6 +46,4 @@ async function handler(req: NextRequest) { } } -const postHandler = defaultResponderForAppDir(handler); - -export { postHandler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/oauth/me/route.ts b/apps/web/app/api/auth/oauth/me/route.ts index 9dc15e5c59..71b9f9fb4d 100644 --- a/apps/web/app/api/auth/oauth/me/route.ts +++ b/apps/web/app/api/auth/oauth/me/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; @@ -14,5 +15,5 @@ async function handler(req: NextRequest) { return NextResponse.json({ username: account.name }, { status: 201 }); } - -export { handler as GET, handler as POST }; +export const GET = defaultResponderForAppDir(handler); +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/oauth/refreshToken/route.ts b/apps/web/app/api/auth/oauth/refreshToken/route.ts index 0dee336ab7..f5650aa7b8 100644 --- a/apps/web/app/api/auth/oauth/refreshToken/route.ts +++ b/apps/web/app/api/auth/oauth/refreshToken/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseUrlFormData } from "app/api/parseRequestData"; import jwt from "jsonwebtoken"; import { NextResponse } from "next/server"; @@ -7,7 +8,7 @@ import prisma from "@calcom/prisma"; import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler"; import type { OAuthTokenPayload } from "@calcom/types/oauth"; -export async function POST(req: NextRequest) { +async function handler(req: NextRequest) { const { client_id, client_secret, grant_type } = await parseUrlFormData(req); if (!client_id || !client_secret) { @@ -63,3 +64,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ access_token }, { status: 200 }); } + +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/oauth/token/route.ts b/apps/web/app/api/auth/oauth/token/route.ts index 8ffb4f9bbe..44251412fe 100644 --- a/apps/web/app/api/auth/oauth/token/route.ts +++ b/apps/web/app/api/auth/oauth/token/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseUrlFormData } from "app/api/parseRequestData"; import jwt from "jsonwebtoken"; import { NextResponse } from "next/server"; @@ -7,7 +8,7 @@ import prisma from "@calcom/prisma"; import { generateSecret } from "@calcom/trpc/server/routers/viewer/oAuth/addClient.handler"; import type { OAuthTokenPayload } from "@calcom/types/oauth"; -export async function POST(req: NextRequest) { +async function handler(req: NextRequest) { const { code, client_id, client_secret, grant_type, redirect_uri } = await parseUrlFormData(req); if (grant_type !== "authorization_code") { return NextResponse.json({ message: "grant_type invalid" }, { status: 400 }); @@ -88,3 +89,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ access_token, refresh_token }, { status: 200 }); } + +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/oidc/route.ts b/apps/web/app/api/auth/oidc/route.ts index ecf4690777..7e9259587b 100644 --- a/apps/web/app/api/auth/oidc/route.ts +++ b/apps/web/app/api/auth/oidc/route.ts @@ -36,6 +36,4 @@ async function handler(req: NextRequest) { } } -const getHandler = defaultResponderForAppDir(handler); - -export { getHandler as GET }; +export const GET = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/reset-password/route.ts b/apps/web/app/api/auth/reset-password/route.ts index f205a86ae6..fc1a745bd1 100644 --- a/apps/web/app/api/auth/reset-password/route.ts +++ b/apps/web/app/api/auth/reset-password/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseRequestData } from "app/api/parseRequestData"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -75,4 +76,4 @@ async function expireResetPasswordRequest(rawRequestId: string) { }); } -export { handler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/saml/authorize/route.ts b/apps/web/app/api/auth/saml/authorize/route.ts index 2a35d9bc1b..cb887f6f86 100644 --- a/apps/web/app/api/auth/saml/authorize/route.ts +++ b/apps/web/app/api/auth/saml/authorize/route.ts @@ -22,8 +22,6 @@ async function handler(req: NextRequest) { } } -const getHandler = defaultResponderForAppDir(handler); - -export { getHandler as GET }; +export const GET = defaultResponderForAppDir(handler); export const dynamic = "force-dynamic"; diff --git a/apps/web/app/api/auth/saml/callback/route.ts b/apps/web/app/api/auth/saml/callback/route.ts index 59c0208b1a..7d12536d31 100644 --- a/apps/web/app/api/auth/saml/callback/route.ts +++ b/apps/web/app/api/auth/saml/callback/route.ts @@ -20,6 +20,4 @@ async function handler(req: NextRequest) { return NextResponse.json({ message: "No redirect URL provided" }, { status: 400 }); } -const postHandler = defaultResponderForAppDir(handler); - -export { postHandler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/saml/token/route.ts b/apps/web/app/api/auth/saml/token/route.ts index 56a4f4fa84..6f2fc94f2e 100644 --- a/apps/web/app/api/auth/saml/token/route.ts +++ b/apps/web/app/api/auth/saml/token/route.ts @@ -12,6 +12,4 @@ async function handler(req: NextRequest) { return NextResponse.json(tokenResponse); } -const postHandler = defaultResponderForAppDir(handler); - -export { postHandler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/saml/userinfo/route.ts b/apps/web/app/api/auth/saml/userinfo/route.ts index 3a02f2f4fe..f752a8881f 100644 --- a/apps/web/app/api/auth/saml/userinfo/route.ts +++ b/apps/web/app/api/auth/saml/userinfo/route.ts @@ -31,8 +31,6 @@ async function handler(req: NextRequest) { return NextResponse.json(userInfo); } -const getHandler = defaultResponderForAppDir(handler); - -export { getHandler as GET }; +export const GET = defaultResponderForAppDir(handler); export const dynamic = "force-dynamic"; diff --git a/apps/web/app/api/auth/setup/route.ts b/apps/web/app/api/auth/setup/route.ts index 3db3e5ae7e..590d02b2d1 100644 --- a/apps/web/app/api/auth/setup/route.ts +++ b/apps/web/app/api/auth/setup/route.ts @@ -59,6 +59,4 @@ async function handler(req: NextRequest) { return NextResponse.json({ message: "First admin user created successfully." }); } -const postHandler = defaultResponderForAppDir(handler); - -export { postHandler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/two-factor/totp/disable/route.ts b/apps/web/app/api/auth/two-factor/totp/disable/route.ts index b9bfceeaac..4fe6507d9c 100644 --- a/apps/web/app/api/auth/two-factor/totp/disable/route.ts +++ b/apps/web/app/api/auth/two-factor/totp/disable/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseRequestData } from "app/api/parseRequestData"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; @@ -13,7 +14,7 @@ import { IdentityProvider } from "@calcom/prisma/client"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function POST(req: NextRequest) { +async function handler(req: NextRequest) { const body = await parseRequestData(req); const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) }); @@ -114,3 +115,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ message: "Two factor disabled" }); } + +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/auth/two-factor/totp/enable/route.ts b/apps/web/app/api/auth/two-factor/totp/enable/route.ts index d1a0e3349c..07551665b4 100644 --- a/apps/web/app/api/auth/two-factor/totp/enable/route.ts +++ b/apps/web/app/api/auth/two-factor/totp/enable/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseRequestData } from "app/api/parseRequestData"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; @@ -11,7 +12,7 @@ import prisma from "@calcom/prisma"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function POST(req: NextRequest) { +async function postHandler(req: NextRequest) { const body = await parseRequestData(req); const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) }); @@ -67,3 +68,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ message: "Two-factor enabled" }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/auth/two-factor/totp/setup/route.ts b/apps/web/app/api/auth/two-factor/totp/setup/route.ts index 0d24983d27..cfd8fc4a5d 100644 --- a/apps/web/app/api/auth/two-factor/totp/setup/route.ts +++ b/apps/web/app/api/auth/two-factor/totp/setup/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseRequestData } from "app/api/parseRequestData"; import crypto from "crypto"; import { cookies, headers } from "next/headers"; @@ -15,7 +16,7 @@ import { IdentityProvider } from "@calcom/prisma/enums"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function POST(req: NextRequest) { +async function postHandler(req: NextRequest) { const body = await parseRequestData(req); const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) }); @@ -81,3 +82,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ secret, keyUri, dataUri, backupCodes }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/availability/calendar/route.ts b/apps/web/app/api/availability/calendar/route.ts index 6ccc587a84..08cdae820a 100644 --- a/apps/web/app/api/availability/calendar/route.ts +++ b/apps/web/app/api/availability/calendar/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -105,4 +106,6 @@ async function deleteHandler(req: NextRequest) { return NextResponse.json({ message: "Calendar Selection Saved" }); } -export { deleteHandler as DELETE, postHandler as POST, getHandler as GET }; +export const POST = defaultResponderForAppDir(postHandler); +export const DELETE = defaultResponderForAppDir(deleteHandler); +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/avatar/[uuid]/route.ts b/apps/web/app/api/avatar/[uuid]/route.ts index d3683ec11e..c6e9a59083 100644 --- a/apps/web/app/api/avatar/[uuid]/route.ts +++ b/apps/web/app/api/avatar/[uuid]/route.ts @@ -63,6 +63,4 @@ async function handler(req: NextRequest, { params }: { params: Params }) { }); } -const getHandler = defaultResponderForAppDir(handler); - -export { getHandler as GET }; +export const GET = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/cancel/route.ts b/apps/web/app/api/cancel/route.ts index c5d9fa0727..be30753fee 100644 --- a/apps/web/app/api/cancel/route.ts +++ b/apps/web/app/api/cancel/route.ts @@ -26,7 +26,5 @@ async function handler(req: NextRequest) { return NextResponse.json(result, { status: statusCode }); } -const deleteHandler = defaultResponderForAppDir(handler); -const postHandler = defaultResponderForAppDir(handler); - -export { deleteHandler as DELETE, postHandler as POST }; +export const DELETE = defaultResponderForAppDir(handler); +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/cron/bookingReminder/route.ts b/apps/web/app/api/cron/bookingReminder/route.ts index 85e6b59e2f..5831b7b06a 100644 --- a/apps/web/app/api/cron/bookingReminder/route.ts +++ b/apps/web/app/api/cron/bookingReminder/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -11,7 +12,7 @@ import { BookingStatus, ReminderType } from "@calcom/prisma/enums"; import type { EventTypeMetadata } from "@calcom/prisma/zod-utils"; import type { CalendarEvent } from "@calcom/types/Calendar"; -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -145,3 +146,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ notificationsSent }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/calendar-cache-cleanup/route.ts b/apps/web/app/api/cron/calendar-cache-cleanup/route.ts index f89da5ed33..96b08bacc0 100644 --- a/apps/web/app/api/cron/calendar-cache-cleanup/route.ts +++ b/apps/web/app/api/cron/calendar-cache-cleanup/route.ts @@ -1,9 +1,10 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import prisma from "@calcom/prisma"; -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (![process.env.CRON_API_KEY, `Bearer ${process.env.CRON_SECRET}`].includes(`${apiKey}`)) { @@ -21,3 +22,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ ok: true, count: deleted.count }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/changeTimeZone/route.ts b/apps/web/app/api/cron/changeTimeZone/route.ts index 8618402795..4df162c498 100644 --- a/apps/web/app/api/cron/changeTimeZone/route.ts +++ b/apps/web/app/api/cron/changeTimeZone/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -20,7 +21,7 @@ const travelScheduleSelect = { }, }; -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -167,3 +168,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ timeZonesChanged }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/downgradeUsers/route.ts b/apps/web/app/api/cron/downgradeUsers/route.ts index 5b8b5348d6..140b42c879 100644 --- a/apps/web/app/api/cron/downgradeUsers/route.ts +++ b/apps/web/app/api/cron/downgradeUsers/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { z } from "zod"; @@ -9,7 +10,7 @@ const querySchema = z.object({ page: z.coerce.number().min(0).optional().default(0), }); -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -51,3 +52,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ ok: true }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/monthlyDigestEmail/route.ts b/apps/web/app/api/cron/monthlyDigestEmail/route.ts index 575c983ba9..c6e959c1db 100644 --- a/apps/web/app/api/cron/monthlyDigestEmail/route.ts +++ b/apps/web/app/api/cron/monthlyDigestEmail/route.ts @@ -1,4 +1,5 @@ import type { Prisma } from "@prisma/client"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { z } from "zod"; @@ -13,7 +14,7 @@ const querySchema = z.object({ page: z.coerce.number().min(0).optional().default(0), }); -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -313,3 +314,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ ok: true }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/syncAppMeta/route.ts b/apps/web/app/api/cron/syncAppMeta/route.ts index 407459544f..c5479da495 100644 --- a/apps/web/app/api/cron/syncAppMeta/route.ts +++ b/apps/web/app/api/cron/syncAppMeta/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -15,7 +16,7 @@ const log = logger.getSubLogger({ * syncAppMeta makes sure any app metadata that has been replicated into the database * remains synchronized with any changes made to the app config files. */ -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const apiKey = request.headers.get("authorization") || request.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -62,3 +63,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ ok: true }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/webhookTriggers/route.ts b/apps/web/app/api/cron/webhookTriggers/route.ts index ddba48018b..b6019f6df0 100644 --- a/apps/web/app/api/cron/webhookTriggers/route.ts +++ b/apps/web/app/api/cron/webhookTriggers/route.ts @@ -1,10 +1,11 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { handleWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/handleWebhookScheduledTriggers"; import prisma from "@calcom/prisma"; -export async function POST(req: NextRequest) { +async function postHandler(req: NextRequest) { const apiKey = req.headers.get("authorization") || req.nextUrl.searchParams.get("apiKey"); if (process.env.CRON_API_KEY !== apiKey) { @@ -15,3 +16,5 @@ export async function POST(req: NextRequest) { return NextResponse.json({ ok: true }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/cron/workflows/scheduleEmailReminders/route.ts b/apps/web/app/api/cron/workflows/scheduleEmailReminders/route.ts index 7208c9b0a7..50278065b8 100644 --- a/apps/web/app/api/cron/workflows/scheduleEmailReminders/route.ts +++ b/apps/web/app/api/cron/workflows/scheduleEmailReminders/route.ts @@ -1,3 +1,5 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; + import { handler } from "@calcom/features/ee/workflows/api/scheduleEmailReminders"; -export { handler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/cron/workflows/scheduleSMSReminders/route.ts b/apps/web/app/api/cron/workflows/scheduleSMSReminders/route.ts index de024832f0..caccba82b5 100644 --- a/apps/web/app/api/cron/workflows/scheduleSMSReminders/route.ts +++ b/apps/web/app/api/cron/workflows/scheduleSMSReminders/route.ts @@ -1,3 +1,5 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; + import { handler } from "@calcom/features/ee/workflows/api/scheduleSMSReminders"; -export { handler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/cron/workflows/scheduleWhatsappReminders/route.ts b/apps/web/app/api/cron/workflows/scheduleWhatsappReminders/route.ts index d0a0d2c60e..95c7f0afa3 100644 --- a/apps/web/app/api/cron/workflows/scheduleWhatsappReminders/route.ts +++ b/apps/web/app/api/cron/workflows/scheduleWhatsappReminders/route.ts @@ -1,3 +1,5 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; + import { handler } from "@calcom/features/ee/workflows/api/scheduleWhatsappReminders"; -export { handler as POST }; +export const POST = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/email/route.ts b/apps/web/app/api/email/route.ts index 04f340a9f5..90d266e117 100644 --- a/apps/web/app/api/email/route.ts +++ b/apps/web/app/api/email/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { NextResponse } from "next/server"; import { renderEmail } from "@calcom/emails"; @@ -7,7 +8,7 @@ import { getTranslation } from "@calcom/lib/server/i18n"; /** * This API endpoint is used for development purposes to preview email templates */ -export async function GET() { +async function getHandler() { // Only allow in development mode if (IS_PRODUCTION) { return new NextResponse("Only for development purposes", { @@ -73,3 +74,5 @@ export async function GET() { return response; } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/future-opt-in/route.ts b/apps/web/app/api/future-opt-in/route.ts index fd3c3d132a..86ebab38b7 100644 --- a/apps/web/app/api/future-opt-in/route.ts +++ b/apps/web/app/api/future-opt-in/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; @@ -6,7 +7,7 @@ import { FUTURE_ROUTES_OVERRIDE_COOKIE_NAME as COOKIE_NAME } from "@calcom/lib/c import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function GET() { +async function getHandler() { const headersList = headers(); const cookiesList = cookies(); const legacyReq = buildLegacyRequest(headersList, cookiesList); @@ -35,3 +36,5 @@ export async function GET() { return response; } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/generate-referral-link/route.ts b/apps/web/app/api/generate-referral-link/route.ts index dd0cb511fb..6649b2c614 100644 --- a/apps/web/app/api/generate-referral-link/route.ts +++ b/apps/web/app/api/generate-referral-link/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; @@ -7,7 +8,7 @@ import prisma from "@calcom/prisma"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function POST() { +async function postHandler() { const headersList = headers(); const cookiesList = cookies(); const legacyReq = buildLegacyRequest(headersList, cookiesList); @@ -51,3 +52,5 @@ export async function POST() { return NextResponse.json({ shortLink }); } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/geolocation/route.ts b/apps/web/app/api/geolocation/route.ts index 0fadae5149..46c2b38587 100644 --- a/apps/web/app/api/geolocation/route.ts +++ b/apps/web/app/api/geolocation/route.ts @@ -1,7 +1,8 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { headers } from "next/headers"; import { NextResponse } from "next/server"; -export async function GET() { +async function getHandler() { const headersList = headers(); const country = headersList.get("x-vercel-ip-country") || "Unknown"; @@ -10,3 +11,5 @@ export async function GET() { return response; } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/intercom-hash/route.ts b/apps/web/app/api/intercom-hash/route.ts index 9212c0afaf..35d01feab2 100644 --- a/apps/web/app/api/intercom-hash/route.ts +++ b/apps/web/app/api/intercom-hash/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; import crypto from "node:crypto"; @@ -6,7 +7,7 @@ import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; -export async function GET() { +async function getHandler() { const headersList = headers(); const cookiesList = cookies(); const legacyReq = buildLegacyRequest(headersList, cookiesList); @@ -28,3 +29,5 @@ export async function GET() { return NextResponse.json({ hash }); } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/logo/route.ts b/apps/web/app/api/logo/route.ts index c7e966aeac..3fccb28427 100644 --- a/apps/web/app/api/logo/route.ts +++ b/apps/web/app/api/logo/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -162,7 +163,7 @@ async function getTeamLogos(subdomain: string, isValidOrgDomain: boolean) { /** * This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo */ -export async function GET(request: NextRequest) { +async function getHandler(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const parsedQuery = logoApiSchema.parse(Object.fromEntries(searchParams.entries())); @@ -217,3 +218,5 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: "Failed fetching logo" }, { status: 404 }); } } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/me/route.ts b/apps/web/app/api/me/route.ts index e21f436331..7e8d816d05 100644 --- a/apps/web/app/api/me/route.ts +++ b/apps/web/app/api/me/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import { NextResponse } from "next/server"; @@ -8,7 +9,7 @@ import { buildLegacyRequest } from "@lib/buildLegacyCtx"; let isCold = true; -export async function GET() { +async function getHandler() { const prePrismaDate = performance.now(); const prisma = (await import("@calcom/prisma")).default; const preSessionDate = performance.now(); @@ -48,3 +49,5 @@ export async function GET() { return response; } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/nope/route.ts b/apps/web/app/api/nope/route.ts index 8888b5c7ab..46a5e30522 100644 --- a/apps/web/app/api/nope/route.ts +++ b/apps/web/app/api/nope/route.ts @@ -1,9 +1,13 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { NextResponse } from "next/server"; -export async function GET() { +async function getHandler() { return NextResponse.json({ message: "Please don't" }, { status: 400 }); } -export async function POST() { +async function postHandler() { return NextResponse.json({ message: "Please don't" }, { status: 400 }); } + +export const GET = defaultResponderForAppDir(getHandler); +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/recorded-daily-video/route.ts b/apps/web/app/api/recorded-daily-video/route.ts index dfff81af43..ee9b6b2884 100644 --- a/apps/web/app/api/recorded-daily-video/route.ts +++ b/apps/web/app/api/recorded-daily-video/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { createHmac } from "crypto"; import { headers } from "next/headers"; import type { NextRequest } from "next/server"; @@ -48,7 +49,7 @@ const getDownloadLinkOfCalVideo = async (recordingId: string) => { return downloadLink; }; -export async function POST(request: NextRequest) { +export async function postHandler(request: NextRequest) { if (!process.env.SENDGRID_API_KEY || !process.env.SENDGRID_EMAIL) { return NextResponse.json({ message: "No SendGrid API key or email" }, { status: 405 }); } @@ -225,3 +226,5 @@ export async function POST(request: NextRequest) { } } } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/scim/v2.0/[...directory]/route.ts b/apps/web/app/api/scim/v2.0/[...directory]/route.ts index 122642a64e..eb52d8433f 100644 --- a/apps/web/app/api/scim/v2.0/[...directory]/route.ts +++ b/apps/web/app/api/scim/v2.0/[...directory]/route.ts @@ -1,4 +1,5 @@ import type { DirectorySyncEvent, DirectorySyncRequest } from "@boxyhq/saml-jackson"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -51,23 +52,23 @@ const handleEvents = async (event: DirectorySyncEvent) => { }; // This is the handler for the SCIM API requests -export async function GET(request: NextRequest, { params }: { params: { directory?: string[] } }) { +async function getHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) { return handleScimRequest(request, "GET", params.directory); } -export async function POST(request: NextRequest, { params }: { params: { directory?: string[] } }) { +async function postHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) { return handleScimRequest(request, "POST", params.directory); } -export async function PUT(request: NextRequest, { params }: { params: { directory?: string[] } }) { +async function putHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) { return handleScimRequest(request, "PUT", params.directory); } -export async function PATCH(request: NextRequest, { params }: { params: { directory?: string[] } }) { +async function patchHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) { return handleScimRequest(request, "PATCH", params.directory); } -export async function DELETE(request: NextRequest, { params }: { params: { directory?: string[] } }) { +async function deleteHandler(request: NextRequest, { params }: { params: { directory?: string[] } }) { return handleScimRequest(request, "DELETE", params.directory); } @@ -139,3 +140,8 @@ async function handleScimRequest(request: NextRequest, method: string, directory return NextResponse.json(data, { status }); } +export const GET = defaultResponderForAppDir(getHandler); +export const POST = defaultResponderForAppDir(postHandler); +export const PATCH = defaultResponderForAppDir(patchHandler); +export const DELETE = defaultResponderForAppDir(deleteHandler); +export const PUT = defaultResponderForAppDir(putHandler); diff --git a/apps/web/app/api/sync/helpscout/route.ts b/apps/web/app/api/sync/helpscout/route.ts index d76b6962c6..c8ad361de0 100644 --- a/apps/web/app/api/sync/helpscout/route.ts +++ b/apps/web/app/api/sync/helpscout/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { createHmac } from "crypto"; import { headers } from "next/headers"; import { cookies } from "next/headers"; @@ -21,7 +22,7 @@ const helpscoutRequestBodySchema = z.object({ * API for Helpscout to retrieve key information about a user from a ticket * Note: HelpScout expects a JSON with a `html` prop to show its content as HTML */ -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const hsSignature = request.headers.get("x-helpscout-signature"); if (!hsSignature) return NextResponse.json({ message: "Missing signature" }, { status: 400 }); @@ -94,3 +95,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ message: "Internal server error" }, { status: 500 }); } } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/tasks/cleanup/route.ts b/apps/web/app/api/tasks/cleanup/route.ts index c7dfb287be..ed474a4859 100644 --- a/apps/web/app/api/tasks/cleanup/route.ts +++ b/apps/web/app/api/tasks/cleanup/route.ts @@ -1 +1,5 @@ -export { GET } from "@calcom/features/tasker/api/cleanup"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; + +import { GET as handler } from "@calcom/features/tasker/api/cleanup"; + +export const GET = defaultResponderForAppDir(handler); diff --git a/apps/web/app/api/tasks/cron/route.ts b/apps/web/app/api/tasks/cron/route.ts index c03ca77b62..8ed16ea2d1 100644 --- a/apps/web/app/api/tasks/cron/route.ts +++ b/apps/web/app/api/tasks/cron/route.ts @@ -1,4 +1,8 @@ -export { GET } from "@calcom/features/tasker/api/cron"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; + +import { GET as handler } from "@calcom/features/tasker/api/cron"; + +export const GET = defaultResponderForAppDir(handler); /** * This runs each minute and we need fresh data each time diff --git a/apps/web/app/api/teams/[team]/upgrade/route.ts b/apps/web/app/api/teams/[team]/upgrade/route.ts index b7ae67c54d..cd0ebcec36 100644 --- a/apps/web/app/api/teams/[team]/upgrade/route.ts +++ b/apps/web/app/api/teams/[team]/upgrade/route.ts @@ -1,3 +1,5 @@ +import type { Params } from "app/_types"; +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -19,7 +21,7 @@ const querySchema = z.object({ session_id: z.string().min(1), }); -export async function GET(req: NextRequest, { params }: { params: { team: string } }) { +async function getHandler(req: NextRequest, { params }: { params: Params }) { try { const searchParams = req.nextUrl.searchParams; const { team: id, session_id } = querySchema.parse({ @@ -102,3 +104,5 @@ export async function GET(req: NextRequest, { params }: { params: { team: string return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); } } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/teams/create/route.ts b/apps/web/app/api/teams/create/route.ts index a14e0e6f73..b4e68fb47e 100644 --- a/apps/web/app/api/teams/create/route.ts +++ b/apps/web/app/api/teams/create/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import type Stripe from "stripe"; @@ -90,4 +91,4 @@ async function getHandler(req: NextRequest) { ); } -export { getHandler as GET }; +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/teams/googleworkspace/add/route.ts b/apps/web/app/api/teams/googleworkspace/add/route.ts index 7ca4ff5ddc..58f01f80ab 100644 --- a/apps/web/app/api/teams/googleworkspace/add/route.ts +++ b/apps/web/app/api/teams/googleworkspace/add/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { OAuth2Client } from "googleapis-common"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -10,7 +11,7 @@ const scopes = [ "https://www.googleapis.com/auth/admin.directory.customer.readonly", ]; -export async function GET(request: NextRequest) { +async function getHandler(request: NextRequest) { try { // Get appKeys from google-calendar const { client_id, client_secret } = await getAppKeysFromSlug("google-calendar"); @@ -44,3 +45,5 @@ export async function GET(request: NextRequest) { ); } } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/teams/googleworkspace/callback/route.ts b/apps/web/app/api/teams/googleworkspace/callback/route.ts index 3be71d23e2..b17b9adb22 100644 --- a/apps/web/app/api/teams/googleworkspace/callback/route.ts +++ b/apps/web/app/api/teams/googleworkspace/callback/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { OAuth2Client } from "googleapis-common"; import { cookies, headers } from "next/headers"; import type { NextRequest } from "next/server"; @@ -17,7 +18,7 @@ const stateSchema = z.object({ teamId: z.string(), }); -export async function GET(request: NextRequest) { +async function getHandler(request: NextRequest) { try { const headersList = headers(); const cookiesList = cookies(); @@ -84,3 +85,5 @@ export async function GET(request: NextRequest) { return NextResponse.redirect(`${WEBAPP_URL}/teams`); } } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/username/route.ts b/apps/web/app/api/username/route.ts index 2f1fde6b35..10d123ecc5 100644 --- a/apps/web/app/api/username/route.ts +++ b/apps/web/app/api/username/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { cookies, headers } from "next/headers"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; @@ -13,7 +14,7 @@ const bodySchema = z.object({ orgSlug: z.string().optional(), }); -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { try { const body = await request.json(); const { username, orgSlug } = bodySchema.parse(body); @@ -31,3 +32,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Failed to check username availability" }, { status: 400 }); } } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/verify-booking-token/route.ts b/apps/web/app/api/verify-booking-token/route.ts index e35834ef12..5fcba32d2f 100644 --- a/apps/web/app/api/verify-booking-token/route.ts +++ b/apps/web/app/api/verify-booking-token/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { parseRequestData } from "app/api/parseRequestData"; import { headers, cookies } from "next/headers"; import type { NextRequest } from "next/server"; @@ -27,7 +28,7 @@ const querySchema = z.object({ userId: z.string(), }); -export async function GET(request: NextRequest) { +async function getHandler(request: NextRequest) { const url = new URL(request.url); const queryParams = Object.fromEntries(request.nextUrl.searchParams.entries()); @@ -50,7 +51,7 @@ export async function GET(request: NextRequest) { } } -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { const url = new URL(request.url); const queryParams = Object.fromEntries(request.nextUrl.searchParams.entries()); @@ -148,3 +149,6 @@ async function handleBookingAction( return NextResponse.redirect(`/booking/${booking.uid}`); } + +export const GET = defaultResponderForAppDir(getHandler); +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/app/api/version/route.ts b/apps/web/app/api/version/route.ts index 2312c2023a..4a30e77191 100644 --- a/apps/web/app/api/version/route.ts +++ b/apps/web/app/api/version/route.ts @@ -1,6 +1,9 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import { NextResponse } from "next/server"; import * as pjson from "package.json"; -export async function GET() { +async function getHandler() { return NextResponse.json({ version: pjson.version }); } + +export const GET = defaultResponderForAppDir(getHandler); diff --git a/apps/web/app/api/webhook/app-credential/route.ts b/apps/web/app/api/webhook/app-credential/route.ts index c0f38d135e..fb81a841fd 100644 --- a/apps/web/app/api/webhook/app-credential/route.ts +++ b/apps/web/app/api/webhook/app-credential/route.ts @@ -1,3 +1,4 @@ +import { defaultResponderForAppDir } from "app/api/defaultResponderForAppDir"; import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import z from "zod"; @@ -16,7 +17,7 @@ const appCredentialWebhookRequestBodySchema = z.object({ keys: z.string(), }); -export async function POST(request: NextRequest) { +async function postHandler(request: NextRequest) { if (!APP_CREDENTIAL_SHARING_ENABLED) { return NextResponse.json({ message: "Credential sharing is not enabled" }, { status: 403 }); } @@ -101,3 +102,5 @@ export async function POST(request: NextRequest) { return NextResponse.json({ message: "Internal server error" }, { status: 500 }); } } + +export const POST = defaultResponderForAppDir(postHandler); diff --git a/apps/web/lib/daily-webhook/tests/recorded-daily-video.test.ts b/apps/web/lib/daily-webhook/tests/recorded-daily-video.test.ts index bc87313008..672fda8317 100644 --- a/apps/web/lib/daily-webhook/tests/recorded-daily-video.test.ts +++ b/apps/web/lib/daily-webhook/tests/recorded-daily-video.test.ts @@ -55,7 +55,7 @@ vi.mock("next/server", async () => { }); // Now import the handler after mocking -const { POST: handler } = recordedDailyVideoRoute; +const { postHandler } = recordedDailyVideoRoute; beforeAll(() => { // Setup env vars @@ -76,6 +76,12 @@ vi.mock("@calcom/lib/videoClient", () => { }; }); +vi.mock("app/api/defaultResponderForAppDir", () => { + return { + defaultResponderForAppDir: vi.fn(), + }; +}); + const BATCH_PROCESSOR_JOB_FINSISHED_PAYLOAD = { version: "1.1.0", type: "batch-processor.job-finished", @@ -273,7 +279,7 @@ describe("Handler: /api/recorded-daily-video", () => { // Handle the response differently try { - const response = await handler(nextReq); + const response = await postHandler(nextReq); // For App Router, we just need to check if the response exists expect(response).toBeDefined(); } catch (error) {