feat: v1 api connected-calendars endpoint (#14176)

* feat: v1 api connected-calendars endpoint

* refactor: flatten return data

* refactor: shorten code

* refactor: document userId for admins
This commit is contained in:
Lauris Skraucis
2024-03-26 13:58:54 +00:00
committed by GitHub
parent af8d38e3a6
commit a8d95d54e7
4 changed files with 174 additions and 1 deletions
@@ -0,0 +1,18 @@
import { z } from "zod";
const CalendarSchema = z.object({
externalId: z.string(),
name: z.string(),
primary: z.boolean(),
readOnly: z.boolean(),
});
const IntegrationSchema = z.object({
name: z.string(),
appId: z.string(),
userId: z.number(),
integration: z.string(),
calendars: z.array(CalendarSchema),
});
export const schemaConnectedCalendarsReadPublic = z.array(IntegrationSchema);
@@ -0,0 +1,146 @@
import type { NextApiRequest } from "next";
import type { UserWithCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
import { getConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { extractUserIdsFromQuery } from "~/lib/utils/extractUserIdsFromQuery";
import { schemaConnectedCalendarsReadPublic } from "~/lib/validations/connected-calendar";
/**
* @swagger
* /connected-calendars:
* get:
* parameters:
* - in: query
* name: apiKey
* required: true
* schema:
* type: string
* description: Your API key
* - in: query
* name: userId
* required: false
* schema:
* type: number
* description: Admins can fetch connected calendars for other user e.g. &userId=1 or multiple users e.g. &userId=1&userId=2
* summary: Fetch connected calendars
* tags:
* - connected-calendars
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* name:
* type: string
* appId:
* type: string
* userId:
* type: number
* integration:
* type: string
* calendars:
* type: array
* items:
* type: object
* properties:
* externalId:
* type: string
* name:
* type: string
* primary:
* type: boolean
* readOnly:
* type: boolean
* examples:
* connectedCalendarExample:
* value: [
* {
* "name": "Google Calendar",
* "appId": "google-calendar",
* "userId": 10,
* "integration": "google_calendar",
* "calendars": [
* {
* "externalId": "alice@gmail.com",
* "name": "alice@gmail.com",
* "primary": true,
* "readOnly": false
* },
* {
* "externalId": "addressbook#contacts@group.v.calendar.google.com",
* "name": "birthdays",
* "primary": false,
* "readOnly": true
* },
* {
* "externalId": "en.latvian#holiday@group.v.calendar.google.com",
* "name": "Holidays in Narnia",
* "primary": false,
* "readOnly": true
* }
* ]
* }
* ]
* 401:
* description: Authorization information is missing or invalid.
* 403:
* description: Non admin user trying to fetch other user's connected calendars.
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
const usersWithCalendars = await prisma.user.findMany({
where: { id: { in: userIds } },
include: {
selectedCalendars: true,
destinationCalendar: true,
},
});
return await getConnectedCalendars(usersWithCalendars);
}
async function getConnectedCalendars(users: UserWithCalendars[]) {
const connectedDestinationCalendarsPromises = users.map((user) =>
getConnectedDestinationCalendars(user, false, prisma).then((connectedCalendarsResult) =>
connectedCalendarsResult.connectedCalendars.map((calendar) => ({
userId: user.id,
...calendar,
}))
)
);
const connectedDestinationCalendars = await Promise.all(connectedDestinationCalendarsPromises);
const flattenedCalendars = connectedDestinationCalendars.flat();
const mapped = flattenedCalendars.map((calendar) => ({
name: calendar.integration.name,
appId: calendar.integration.slug,
userId: calendar.userId,
integration: calendar.integration.type,
calendars: (calendar.calendars ?? []).map((c) => ({
externalId: c.externalId,
name: c.name,
primary: c.primary ?? false,
readOnly: c.readOnly,
})),
}));
return schemaConnectedCalendarsReadPublic.parse(mapped);
}
export default defaultResponder(getHandler);
@@ -0,0 +1,9 @@
import { defaultHandler } from "@calcom/lib/server";
import { withMiddleware } from "~/lib/helpers/withMiddleware";
export default withMiddleware()(
defaultHandler({
GET: import("./_get"),
})
);
@@ -4,7 +4,7 @@ import type { DestinationCalendar, SelectedCalendar, User } from "@calcom/prisma
import { AppCategories } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
type UserWithCalendars = Pick<User, "id"> & {
export type UserWithCalendars = Pick<User, "id"> & {
selectedCalendars: Pick<SelectedCalendar, "externalId" | "integration">[];
destinationCalendar: DestinationCalendar | null;
};