diff --git a/apps/web/app/api/availability/calendar/route.ts b/apps/web/app/api/availability/calendar/route.ts new file mode 100644 index 0000000000..91e37181a4 --- /dev/null +++ b/apps/web/app/api/availability/calendar/route.ts @@ -0,0 +1,123 @@ +import { cookies, headers } from "next/headers"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { z } from "zod"; + +import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; +import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; +import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache"; +import { HttpError } from "@calcom/lib/http-error"; +import notEmpty from "@calcom/lib/notEmpty"; +import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; +import { UserRepository } from "@calcom/lib/server/repository/user"; + +import { buildLegacyRequest } from "@lib/buildLegacyCtx"; + +const selectedCalendarSelectSchema = z.object({ + integration: z.string(), + externalId: z.string(), + credentialId: z.coerce.number(), + eventTypeId: z.coerce.number().nullish(), +}); + +async function authMiddleware() { + const session = await getServerSession({ req: buildLegacyRequest(headers(), cookies()) }); + + if (!session?.user?.id) { + throw new HttpError({ statusCode: 401, message: "Not authenticated" }); + } + + const userWithCredentials = await UserRepository.findUserWithCredentials({ id: session.user.id }); + + if (!userWithCredentials) { + throw new HttpError({ statusCode: 401, message: "Not authenticated" }); + } + + return userWithCredentials; +} + +// 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(); + + 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 }; + }); + + return NextResponse.json(selectableCalendars); + } catch (error) { + if (error instanceof HttpError) { + throw error; + } + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} + +export async function POST(req: NextRequest) { + try { + const user = await authMiddleware(); + const body = await req.json(); + const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse(body); + + await SelectedCalendarRepository.upsert({ + userId: user.id, + integration, + externalId, + credentialId, + 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 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 }); + } +} diff --git a/apps/web/pages/api/availability/calendar.ts b/apps/web/pages/api/availability/calendar.ts deleted file mode 100644 index 3d975dfc48..0000000000 --- a/apps/web/pages/api/availability/calendar.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { z } from "zod"; - -import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager"; -import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; -import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache"; -import { HttpError } from "@calcom/lib/http-error"; -import notEmpty from "@calcom/lib/notEmpty"; -import { defaultHandler, defaultResponder } from "@calcom/lib/server"; -import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar"; -import { UserRepository } from "@calcom/lib/server/repository/user"; - -const selectedCalendarSelectSchema = z.object({ - integration: z.string(), - externalId: z.string(), - credentialId: z.coerce.number(), - eventTypeId: z.coerce.number().nullish(), -}); - -/** Shared authentication middleware for GET, DELETE and POST requests */ -async function authMiddleware(req: CustomNextApiRequest) { - const session = await getServerSession({ req }); - - if (!session?.user?.id) { - throw new HttpError({ statusCode: 401, message: "Not authenticated" }); - } - - const userWithCredentials = await UserRepository.findUserWithCredentials({ id: session.user.id }); - - if (!userWithCredentials) { - throw new HttpError({ statusCode: 401, message: "Not authenticated" }); - } - req.userWithCredentials = userWithCredentials; - return userWithCredentials; -} - -type CustomNextApiRequest = NextApiRequest & { - userWithCredentials?: Awaited>; -}; - -async function postHandler(req: CustomNextApiRequest) { - if (!req.userWithCredentials) throw new HttpError({ statusCode: 401, message: "Not authenticated" }); - const user = req.userWithCredentials; - const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse(req.body); - await SelectedCalendarRepository.upsert({ - userId: user.id, - integration, - externalId, - credentialId, - eventTypeId: eventTypeId ?? null, - }); - - return { message: "Calendar Selection Saved" }; -} - -async function deleteHandler(req: CustomNextApiRequest) { - if (!req.userWithCredentials) throw new HttpError({ statusCode: 401, message: "Not authenticated" }); - const user = req.userWithCredentials; - const { integration, externalId, credentialId, eventTypeId } = selectedCalendarSelectSchema.parse( - req.query - ); - 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 { message: "Calendar Selection Saved" }; -} - -// 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 -async function getHandler(req: CustomNextApiRequest) { - if (!req.userWithCredentials) throw new HttpError({ statusCode: 401, message: "Not authenticated" }); - const user = req.userWithCredentials; - 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 }; - }); - return selectableCalendars; -} - -export default defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => { - await authMiddleware(req); - return defaultHandler({ - GET: Promise.resolve({ default: defaultResponder(getHandler) }), - POST: Promise.resolve({ default: defaultResponder(postHandler) }), - DELETE: Promise.resolve({ default: defaultResponder(deleteHandler) }), - })(req, res); -});