Files
calendar/packages/trpc/server/routers/loggedInViewer/setDestinationCalendar.handler.ts
T
3c1297aa72 fix: calcom trpc sessio circle dep (#19893)
* fix trpc session circle dep

* fixes trpc session cirlce dep

* fix relative imports

* fix more imports to use types and not trpc

* fix exports

* Fixed types

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2025-03-19 05:50:22 -03:00

124 lines
4.4 KiB
TypeScript

import { getCalendarCredentials, getConnectedCalendars } from "@calcom/lib/CalendarManager";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { DestinationCalendarRepository } from "@calcom/lib/server/repository/destinationCalendar";
import { prisma } from "@calcom/prisma";
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
import { TRPCError } from "@trpc/server";
import type { TSetDestinationCalendarInputSchema } from "./setDestinationCalendar.schema";
type SessionUser = NonNullable<TrpcSessionUser>;
type User = {
id: SessionUser["id"];
email: SessionUser["email"];
userLevelSelectedCalendars: SessionUser["userLevelSelectedCalendars"];
};
type SetDestinationCalendarOptions = {
ctx: {
user: User;
};
input: TSetDestinationCalendarInputSchema;
};
type ConnectedCalendar = Awaited<ReturnType<typeof getConnectedCalendars>>["connectedCalendars"][number];
type ConnectedCalendarCalendar = NonNullable<ConnectedCalendar["calendars"]>[number];
export const getFirstConnectedCalendar = ({
connectedCalendars,
matcher,
}: {
connectedCalendars: ConnectedCalendar[];
matcher: (calendar: ConnectedCalendarCalendar) => boolean;
}) => {
const calendars = connectedCalendars.flatMap((c) => c.calendars ?? []);
const matchingCalendars = calendars.filter(matcher);
const delegationCredentialCalendar = matchingCalendars.find((cal) => !!cal.delegationCredentialId);
// Prefer Delegation credential calendar as there could be other one due to existing connections even after DelegationCredential is enabled.
if (delegationCredentialCalendar) {
return delegationCredentialCalendar;
} else {
return matchingCalendars[0];
}
};
/**
* It identifies the destination calendar by externalId, integration and eventTypeId and doesn't consider the `credentialId` or destinationCalendar.id
* Also, DestinationCalendar doesn't have unique constraint on externalId, integration and eventTypeId, so there could be multiple destinationCalendars with same externalId, integration and eventTypeId in DB.
* So, it could update any of the destinationCalendar when there are duplicates in DB. Ideally we should have unique constraint on externalId, integration and eventTypeId.
*
* With the addition of Delegation credential, it adds another dimension to the problem.
* A user could have DelegationCredential and non-Delegation credential for the same calendar and he might be selecting Delegation credential connected calendar but it could still be set with nullish destinationCalendar.delegationCredentialId.
*/
export const setDestinationCalendarHandler = async ({ ctx, input }: SetDestinationCalendarOptions) => {
const { user } = ctx;
const { integration, externalId, eventTypeId } = input;
const credentials = await getUsersCredentials(user);
const calendarCredentials = getCalendarCredentials(credentials);
const { connectedCalendars } = await getConnectedCalendars(
calendarCredentials,
user.userLevelSelectedCalendars
);
const allCals = connectedCalendars.map((cal) => cal.calendars ?? []).flat();
const firstConnectedCalendar = getFirstConnectedCalendar({
connectedCalendars,
matcher: (cal) =>
cal.externalId === externalId && cal.integration === integration && cal.readOnly === false,
});
const { credentialId, delegationCredentialId } = firstConnectedCalendar || {};
let where;
if (!credentialId && !delegationCredentialId) {
throw new TRPCError({ code: "BAD_REQUEST", message: `Could not find calendar ${input.externalId}` });
}
const primaryEmail =
allCals.find(
(cal) =>
cal.primary &&
(cal.credentialId === credentialId || cal.delegationCredentialId === delegationCredentialId)
)?.email ?? null;
if (eventTypeId) {
if (
!(await prisma.eventType.findFirst({
where: {
id: eventTypeId,
userId: user.id,
},
}))
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: `You don't have access to event type ${eventTypeId}`,
});
}
where = { eventTypeId };
} else where = { userId: user.id };
await DestinationCalendarRepository.upsert({
where,
update: {
integration,
externalId,
primaryEmail,
credentialId,
delegationCredentialId,
},
create: {
...where,
integration,
externalId,
primaryEmail,
credentialId,
delegationCredentialId,
},
});
};