fix: Duplicate calendar events (#11327)

Co-authored-by: Shivam Kalra <shivamkalra98@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Alan <alannnc@gmail.com>
This commit is contained in:
Joe Au-Yeung 2023-09-14 12:53:58 -04:00 committed by GitHub
parent add297b09a
commit 197b435b6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 313 additions and 212 deletions

View File

@ -5,6 +5,7 @@ import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/Cale
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import notEmpty from "@calcom/lib/notEmpty";
import prisma from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
const selectedCalendarSelectSchema = z.object({
integration: z.string(),
@ -25,7 +26,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
id: session.user.id,
},
select: {
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
timeZone: true,
id: true,
selectedCalendars: true,

View File

@ -16,6 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
id: req.session?.user?.id,
},
select: {
email: true,
id: true,
},
});
@ -36,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const dav = new CalendarService({
id: 0,
...data,
user: { email: user.email },
});
await dav?.listCalendars();
await prisma.credential.create({

View File

@ -1,4 +1,5 @@
import type { PrismaClient } from "@calcom/prisma/client";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { TRPCError } from "@calcom/trpc/server";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -24,6 +25,7 @@ export const projectMutationHandler = async ({ ctx, input }: ProjectMutationHand
where: {
userId: user?.id,
},
select: credentialForCalendarServiceSelect,
});
if (!credential) {

View File

@ -1,4 +1,5 @@
import type { PrismaClient } from "@calcom/prisma/client";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { TRPCError } from "@calcom/trpc/server";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -20,6 +21,7 @@ export const projectHandler = async ({ ctx }: ProjectsHandlerOptions) => {
where: {
userId: user?.id,
},
select: credentialForCalendarServiceSelect,
});
if (!credential) {
throw new TRPCError({ code: "FORBIDDEN", message: "No credential found for user" });

View File

@ -17,6 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
select: {
id: true,
email: true,
},
});
@ -36,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const dav = new CalendarService({
id: 0,
...data,
user: { email: user.email },
});
await dav?.listCalendars();
await prisma.credential.create({

View File

@ -60,6 +60,7 @@ export const FAKE_DAILY_CREDENTIAL: CredentialPayload & { invalid: boolean } = {
type: "daily_video",
key: { apikey: process.env.DAILY_API_KEY },
userId: 0,
user: { email: "" },
appId: "daily-video",
invalid: false,
teamId: null,

View File

@ -26,6 +26,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
},
select: {
id: true,
email: true,
},
});
@ -42,6 +43,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const dav = new CalendarService({
id: 0,
...data,
user: { email: user.email },
});
await dav?.listCalendars();
await prisma.credential.create({

View File

@ -26,6 +26,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
},
select: {
id: true,
email: true,
},
});
@ -41,6 +42,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
try {
const dav = new CalendarService({
id: 0,
user: { email: user.email },
...data,
});
await dav?.listCalendars();

View File

@ -36,7 +36,7 @@ export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
};
try {
const service = new CalendarService({ id: 0, ...data });
const service = new CalendarService({ id: 0, user: { email: session.user.email || "" }, ...data });
await service?.listCalendars();
await prisma.credential.create({ data });
} catch (reason) {

View File

@ -28,11 +28,13 @@ export default class GoogleCalendarService implements Calendar {
private integrationName = "";
private auth: { getToken: () => Promise<MyGoogleAuth> };
private log: typeof logger;
private credential: CredentialPayload;
constructor(credential: CredentialPayload) {
this.integrationName = "google_calendar";
this.auth = this.googleAuth(credential);
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
this.credential = credential;
}
private googleAuth = (credential: CredentialPayload) => {
@ -84,22 +86,50 @@ export default class GoogleCalendarService implements Calendar {
};
};
async createEvent(calEventRaw: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
const eventAttendees = calEventRaw.attendees.map(({ id: _id, ...rest }) => ({
private getAttendees = (event: CalendarEvent) => {
// When rescheduling events we know the external id of the calendar so we can just look for it in the destinationCalendar array.
const selectedHostDestinationCalendar = event.destinationCalendar?.find(
(cal) => cal.credentialId === this.credential.id
);
const eventAttendees = event.attendees.map(({ id: _id, ...rest }) => ({
...rest,
responseStatus: "accepted",
}));
// TODO: Check every other CalendarService for team members
const teamMembers =
calEventRaw.team?.members.map((m) => ({
email: m.email,
displayName: m.name,
const attendees: calendar_v3.Schema$EventAttendee[] = [
{
...event.organizer,
id: String(event.organizer.id),
responseStatus: "accepted",
})) || [];
organizer: true,
// Tried changing the display name to the user but GCal will not let you do that. It will only display the name of the external calendar. Leaving this in just incase it works in the future.
displayName: event.organizer.name,
email: selectedHostDestinationCalendar?.externalId ?? event.organizer.email,
},
...eventAttendees,
];
if (event.team?.members) {
// TODO: Check every other CalendarService for team members
const teamAttendeesWithoutCurrentUser = event.team.members
.filter((member) => member.email !== this.credential.user?.email)
.map((m) => {
const teamMemberDestinationCalendar = event.destinationCalendar?.find(
(calendar) => calendar.integration === "google_calendar" && calendar.userId === m.id
);
return {
email: teamMemberDestinationCalendar?.externalId ?? m.email,
displayName: m.name,
responseStatus: "accepted",
};
});
attendees.push(...teamAttendeesWithoutCurrentUser);
}
return attendees;
};
async createEvent(calEventRaw: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
return new Promise(async (resolve, reject) => {
const selectedHostDestinationCalendar = calEventRaw.destinationCalendar?.find(
(cal) => cal.credentialId === credentialId
);
const myGoogleAuth = await this.auth.getToken();
const payload: calendar_v3.Schema$Event = {
summary: calEventRaw.title,
@ -112,19 +142,7 @@ export default class GoogleCalendarService implements Calendar {
dateTime: calEventRaw.endTime,
timeZone: calEventRaw.organizer.timeZone,
},
attendees: [
{
...calEventRaw.organizer,
id: String(calEventRaw.organizer.id),
responseStatus: "accepted",
organizer: true,
email: selectedHostDestinationCalendar?.externalId
? selectedHostDestinationCalendar.externalId
: calEventRaw.organizer.email,
},
...eventAttendees,
...teamMembers,
],
attendees: this.getAttendees(calEventRaw),
reminders: {
useDefault: true,
},
@ -194,19 +212,7 @@ export default class GoogleCalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
return new Promise(async (resolve, reject) => {
const [mainHostDestinationCalendar] =
event?.destinationCalendar && event?.destinationCalendar.length > 0 ? event.destinationCalendar : [];
const myGoogleAuth = await this.auth.getToken();
const eventAttendees = event.attendees.map(({ ...rest }) => ({
...rest,
responseStatus: "accepted",
}));
const teamMembers =
event.team?.members.map((m) => ({
email: m.email,
displayName: m.name,
responseStatus: "accepted",
})) || [];
const payload: calendar_v3.Schema$Event = {
summary: event.title,
description: getRichDescription(event),
@ -218,19 +224,7 @@ export default class GoogleCalendarService implements Calendar {
dateTime: event.endTime,
timeZone: event.organizer.timeZone,
},
attendees: [
{
...event.organizer,
id: String(event.organizer.id),
organizer: true,
responseStatus: "accepted",
email: mainHostDestinationCalendar?.externalId
? mainHostDestinationCalendar.externalId
: event.organizer.email,
},
...(eventAttendees as any),
...(teamMembers as any),
],
attendees: this.getAttendees(event),
reminders: {
useDefault: true,
},

View File

@ -34,11 +34,13 @@ export default class LarkCalendarService implements Calendar {
private integrationName = "";
private log: typeof logger;
auth: { getToken: () => Promise<string> };
private credential: CredentialPayload;
constructor(credential: CredentialPayload) {
this.integrationName = "lark_calendar";
this.auth = this.larkAuth(credential);
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
this.credential = credential;
}
private larkAuth = (credential: CredentialPayload) => {
@ -122,10 +124,13 @@ export default class LarkCalendarService implements Calendar {
});
};
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
async createEvent(event: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
let eventId = "";
let eventRespData;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const mainHostDestinationCalendar = event.destinationCalendar
? event.destinationCalendar.find((cal) => cal.credentialId === this.credential.id) ??
event.destinationCalendar[0]
: undefined;
const calendarId = mainHostDestinationCalendar?.externalId;
if (!calendarId) {
throw new Error("no calendar id");
@ -143,7 +148,7 @@ export default class LarkCalendarService implements Calendar {
}
try {
await this.createAttendees(event, eventId);
await this.createAttendees(event, eventId, credentialId);
return {
...eventRespData,
uid: eventRespData.data.event.event_id as string,
@ -160,8 +165,11 @@ export default class LarkCalendarService implements Calendar {
}
}
private createAttendees = async (event: CalendarEvent, eventId: string) => {
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
private createAttendees = async (event: CalendarEvent, eventId: string, credentialId: number) => {
const mainHostDestinationCalendar = event.destinationCalendar
? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ??
event.destinationCalendar[0]
: undefined;
const calendarId = mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in createAttendees");
@ -189,7 +197,9 @@ export default class LarkCalendarService implements Calendar {
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId?: string) {
const eventId = uid;
let eventRespData;
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const mainHostDestinationCalendar = event.destinationCalendar?.find(
(cal) => cal.externalId === externalCalendarId
);
const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in updateEvent");
@ -234,7 +244,9 @@ export default class LarkCalendarService implements Calendar {
* @returns
*/
async deleteEvent(uid: string, event: CalendarEvent, externalCalendarId?: string) {
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const mainHostDestinationCalendar = event.destinationCalendar?.find(
(cal) => cal.externalId === externalCalendarId
);
const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
if (!calendarId) {
this.log.error("no calendar id provided in deleteEvent");
@ -393,13 +405,16 @@ export default class LarkCalendarService implements Calendar {
attendeeArray.push(attendee);
});
event.team?.members.forEach((member) => {
const attendee: LarkEventAttendee = {
type: "third_party",
is_optional: false,
third_party_email: member.email,
};
attendeeArray.push(attendee);
if (member.email !== this.credential.user?.email) {
const attendee: LarkEventAttendee = {
type: "third_party",
is_optional: false,
third_party_email: member.email,
};
attendeeArray.push(attendee);
}
});
return attendeeArray;
};
}

View File

@ -61,16 +61,20 @@ export default class Office365CalendarService implements Calendar {
private accessToken: string | null = null;
auth: { getToken: () => Promise<string> };
private apiGraphUrl = "https://graph.microsoft.com/v1.0";
private credential: CredentialPayload;
constructor(credential: CredentialPayload) {
this.integrationName = "office365_calendar";
this.auth = this.o365Auth(credential);
this.credential = credential;
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
}
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
async createEvent(event: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
const mainHostDestinationCalendar = event.destinationCalendar
? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ??
event.destinationCalendar[0]
: undefined;
try {
const eventsUrl = mainHostDestinationCalendar?.externalId
? `/me/calendars/${mainHostDestinationCalendar?.externalId}/events`
@ -295,6 +299,16 @@ export default class Office365CalendarService implements Calendar {
timeZone: event.organizer.timeZone,
},
attendees: [
// Add the calEvent organizer
{
emailAddress: {
address: event.destinationCalendar
? event.destinationCalendar.find((cal) => cal.userId === event.organizer.id)?.externalId ??
event.organizer.email
: event.organizer.email,
name: event.organizer.name,
},
},
...event.attendees.map((attendee) => ({
emailAddress: {
address: attendee.email,
@ -303,13 +317,20 @@ export default class Office365CalendarService implements Calendar {
type: "required",
})),
...(event.team?.members
? event.team?.members.map((member) => ({
emailAddress: {
address: member.email,
name: member.name,
},
type: "required",
}))
? event.team?.members
.filter((member) => member.email !== this.credential.user?.email)
.map((member) => {
const destinationCalendar =
event.destinationCalendar &&
event.destinationCalendar.find((cal) => cal.userId === member.id);
return {
emailAddress: {
address: destinationCalendar?.externalId ?? member.email,
name: member.name,
},
type: "required",
};
})
: []),
],
location: event.location ? { displayName: getLocation(event) } : undefined,

View File

@ -5,6 +5,7 @@ import { defaultVideoAppCategories } from "@calcom/app-store/utils";
import getEnabledAppsFromCredentials from "@calcom/lib/apps/getEnabledAppsFromCredentials";
import { prisma } from "@calcom/prisma";
import { AppCategories } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { defaultLocations } from "./locations";
@ -62,13 +63,7 @@ export async function getLocationGroupedOptions(
},
},
select: {
id: true,
type: true,
key: true,
userId: true,
teamId: true,
appId: true,
invalid: true,
...credentialForCalendarServiceSelect,
team: {
select: {
name: true,

View File

@ -1,11 +1,11 @@
import type { AppCategories } from "@prisma/client";
import { Prisma } from "@prisma/client";
// If you import this file on any app it should produce circular dependency
// import appStore from "./index";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import type { EventLocationType } from "@calcom/app-store/locations";
import type { App, AppMeta } from "@calcom/types/App";
import type { CredentialPayload } from "@calcom/types/Credential";
export * from "./_utils/getEventTypeAppData";
@ -30,13 +30,7 @@ const ALL_APPS_MAP = Object.keys(appStoreMetadata).reduce((store, key) => {
return store;
}, {} as Record<string, AppMeta>);
const credentialData = Prisma.validator<Prisma.CredentialArgs>()({
select: { id: true, type: true, key: true, userId: true, teamId: true, appId: true, invalid: true },
});
export type CredentialData = Prisma.CredentialGetPayload<typeof credentialData>;
export type CredentialDataWithTeamName = CredentialData & {
export type CredentialDataWithTeamName = CredentialPayload & {
team?: {
name: string;
} | null;
@ -64,6 +58,7 @@ function getApps(credentials: CredentialDataWithTeamName[], filterOnCredentials?
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
key: appMeta.key!,
userId: 0,
user: { email: "" },
teamId: null,
appId: appMeta.slug,
invalid: false,

View File

@ -14,7 +14,7 @@ import type {
IntegrationCalendar,
NewCalendarEventType,
} from "@calcom/types/Calendar";
import type { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { EventResult } from "@calcom/types/EventManager";
import getCalendarsEvents from "./getCalendarsEvents";
@ -216,7 +216,7 @@ export const getBusyCalendarTimes = async (
};
export const createEvent = async (
credential: CredentialWithAppName,
credential: CredentialPayload,
calEvent: CalendarEvent,
externalId?: string
): Promise<EventResult<NewCalendarEventType>> => {
@ -255,7 +255,7 @@ export const createEvent = async (
: undefined;
return {
appName: credential.appName,
appName: credential.appId || "",
type: credential.type,
success,
uid,
@ -270,7 +270,7 @@ export const createEvent = async (
};
export const updateEvent = async (
credential: CredentialWithAppName,
credential: CredentialPayload,
calEvent: CalendarEvent,
bookingRefUid: string | null,
externalCalendarId: string | null
@ -311,7 +311,7 @@ export const updateEvent = async (
}
return {
appName: credential.appName,
appName: credential.appId || "",
type: credential.type,
success,
uid,

View File

@ -1,4 +1,4 @@
import type { DestinationCalendar, Booking } from "@prisma/client";
import type { Booking, DestinationCalendar } from "@prisma/client";
// eslint-disable-next-line no-restricted-imports
import { cloneDeep, merge } from "lodash";
import { v5 as uuidv5 } from "uuid";
@ -7,18 +7,21 @@ import type { z } from "zod";
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter";
import { appKeysSchema as calVideoKeysSchema } from "@calcom/app-store/dailyvideo/zod";
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
import { MeetLocationType } from "@calcom/app-store/locations";
import { getEventLocationTypeFromApp, MeetLocationType } from "@calcom/app-store/locations";
import getApps from "@calcom/app-store/utils";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { createdEventSchema } from "@calcom/prisma/zod-utils";
import type { NewCalendarEventType } from "@calcom/types/Calendar";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { Event } from "@calcom/types/Event";
import type { EventResult } from "@calcom/types/EventManager";
import type { CreateUpdateResult, PartialBooking, PartialReference } from "@calcom/types/EventManager";
import type {
CreateUpdateResult,
EventResult,
PartialBooking,
PartialReference,
} from "@calcom/types/EventManager";
import { createEvent, updateEvent } from "./CalendarManager";
import { createMeeting, updateMeeting } from "./videoClient";
@ -68,8 +71,8 @@ export type EventManagerUser = {
type createdEventSchema = z.infer<typeof createdEventSchema>;
export default class EventManager {
calendarCredentials: CredentialWithAppName[];
videoCredentials: CredentialWithAppName[];
calendarCredentials: CredentialPayload[];
videoCredentials: CredentialPayload[];
/**
* Takes an array of credentials and initializes a new instance of the EventManager.
@ -338,26 +341,37 @@ export default class EventManager {
private async createAllCalendarEvents(event: CalendarEvent) {
let createdEvents: EventResult<NewCalendarEventType>[] = [];
if (event.destinationCalendar && event.destinationCalendar.length > 0) {
for (const destination of event.destinationCalendar) {
// Since GCal pushes events to multiple calendars we only want to create one event per booking
let gCalAdded = false;
const destinationCalendars: DestinationCalendar[] = event.destinationCalendar.reduce(
(destinationCals, cal) => {
if (cal.integration === "google_calendar") {
if (gCalAdded) {
return destinationCals;
} else {
gCalAdded = true;
destinationCals.push(cal);
}
} else {
destinationCals.push(cal);
}
return destinationCals;
},
[] as DestinationCalendar[]
);
for (const destination of destinationCalendars) {
if (destination.credentialId) {
let credential = this.calendarCredentials.find((c) => c.id === destination.credentialId);
if (!credential) {
// Fetch credential from DB
const credentialFromDB = await prisma.credential.findUnique({
include: {
app: {
select: {
slug: true,
},
},
},
where: {
id: destination.credentialId,
},
select: credentialForCalendarServiceSelect,
});
if (credentialFromDB && credentialFromDB.app?.slug) {
if (credentialFromDB && credentialFromDB.appId) {
credential = {
appName: credentialFromDB?.app.slug ?? "",
id: credentialFromDB.id,
type: credentialFromDB.type,
key: credentialFromDB.key,
@ -365,6 +379,7 @@ export default class EventManager {
teamId: credentialFromDB.teamId,
invalid: credentialFromDB.invalid,
appId: credentialFromDB.appId,
user: credentialFromDB.user,
};
}
}
@ -416,7 +431,7 @@ export default class EventManager {
* @private
*/
private getVideoCredential(event: CalendarEvent): CredentialWithAppName | undefined {
private getVideoCredential(event: CalendarEvent): CredentialPayload | undefined {
if (!event.location) {
return undefined;
}
@ -444,7 +459,7 @@ export default class EventManager {
event.location +
" because credential is missing for the app"
);
videoCredential = { ...FAKE_DAILY_CREDENTIAL, appName: "FAKE" };
videoCredential = { ...FAKE_DAILY_CREDENTIAL };
}
return videoCredential;
@ -524,20 +539,13 @@ export default class EventManager {
if (!credential) {
// Fetch credential from DB
const credentialFromDB = await prisma.credential.findUnique({
include: {
app: {
select: {
slug: true,
},
},
},
where: {
id: reference.credentialId,
},
select: credentialForCalendarServiceSelect,
});
if (credentialFromDB && credentialFromDB.app?.slug) {
if (credentialFromDB && credentialFromDB.appId) {
credential = {
appName: credentialFromDB?.app.slug ?? "",
id: credentialFromDB.id,
type: credentialFromDB.type,
key: credentialFromDB.key,
@ -545,6 +553,7 @@ export default class EventManager {
teamId: credentialFromDB.teamId,
invalid: credentialFromDB.invalid,
appId: credentialFromDB.appId,
user: credentialFromDB.user,
};
}
}
@ -567,6 +576,7 @@ export default class EventManager {
where: {
id: oldCalendarEvent.credentialId,
},
select: credentialForCalendarServiceSelect,
});
const calendar = await getCalendar(calendarCredential);
await calendar?.deleteEvent(oldCalendarEvent.uid, event, oldCalendarEvent.externalCalendarId);
@ -582,7 +592,7 @@ export default class EventManager {
if (!calendarReference) {
return {
appName: cred.appName,
appName: cred.appId || "",
type: cred.type,
success: false,
uid: "",

View File

@ -1,4 +1,4 @@
import type { Booking, Credential, EventType } from "@prisma/client";
import type { Booking, EventType } from "@prisma/client";
import { getBusyCalendarTimes } from "@calcom/core/CalendarManager";
import dayjs from "@calcom/dayjs";
@ -9,9 +9,10 @@ import prisma from "@calcom/prisma";
import type { SelectedCalendar } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
import type { EventBusyDetails } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export async function getBusyTimes(params: {
credentials: Credential[];
credentials: CredentialPayload[];
userId: number;
userEmail: string;
username: string;

View File

@ -14,6 +14,7 @@ import { performance } from "@calcom/lib/server/perfObserver";
import { getTotalBookingDuration } from "@calcom/lib/server/queries";
import prisma, { availabilityUserSelect } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { EventTypeMetaDataSchema, stringToDayjs } from "@calcom/prisma/zod-utils";
import type {
EventBusyDate,
@ -81,7 +82,9 @@ const getUser = (where: Prisma.UserWhereInput) =>
where,
select: {
...availabilityUserSelect,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
},
});

View File

@ -9,7 +9,7 @@ import logger from "@calcom/lib/logger";
import { prisma } from "@calcom/prisma";
import type { GetRecordingsResponseSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
import type { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import type { VideoApiAdapter, VideoApiAdapterFactory, VideoCallData } from "@calcom/types/VideoApiAdapter";
@ -48,7 +48,7 @@ const getBusyVideoTimes = async (withCredentials: CredentialPayload[]) =>
results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[])
);
const createMeeting = async (credential: CredentialWithAppName, calEvent: CalendarEvent) => {
const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEvent) => {
const uid: string = getUid(calEvent);
if (!credential || !credential.appId) {
@ -69,7 +69,7 @@ const createMeeting = async (credential: CredentialWithAppName, calEvent: Calend
createdEvent: VideoCallData | undefined;
credentialId: number;
} = {
appName: credential.appName,
appName: credential.appId || "",
type: credential.type,
uid,
originalEvent: calEvent,
@ -110,7 +110,7 @@ const createMeeting = async (credential: CredentialWithAppName, calEvent: Calend
};
const updateMeeting = async (
credential: CredentialWithAppName,
credential: CredentialPayload,
calEvent: CalendarEvent,
bookingRef: PartialReference | null
): Promise<EventResult<VideoCallData>> => {
@ -131,7 +131,7 @@ const updateMeeting = async (
if (!updatedMeeting) {
return {
appName: credential.appName,
appName: credential.appId || "",
type: credential.type,
success,
uid,
@ -140,7 +140,7 @@ const updateMeeting = async (
}
return {
appName: credential.appName,
appName: credential.appId || "",
type: credential.type,
success,
uid,
@ -176,6 +176,7 @@ const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => {
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
@ -200,6 +201,7 @@ const getRecordingsOfCalVideoByRoomName = async (
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
@ -222,6 +224,7 @@ const getDownloadLinkOfCalVideoByRecordingId = async (recordingId: string) => {
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,

View File

@ -26,7 +26,8 @@ import { handleRefundError } from "@calcom/lib/payment/handleRefundError";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus, MembershipRole, WorkflowMethods, WebhookTriggerEvents } from "@calcom/prisma/enums";
import { BookingStatus, MembershipRole, WebhookTriggerEvents, WorkflowMethods } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { schemaBookingCancelParams } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService, PaymentApp } from "@calcom/types/PaymentService";
@ -44,7 +45,7 @@ async function getBookingToDelete(id: number | undefined, uid: string | undefine
user: {
select: {
id: true,
credentials: true, // Not leaking at the moment, be careful with
credentials: { select: credentialForCalendarServiceSelect }, // Not leaking at the moment, be careful with
email: true,
timeZone: true,
timeFormat: true,
@ -434,6 +435,7 @@ async function handler(req: CustomRequest) {
where: {
id: credentialId,
},
select: credentialForCalendarServiceSelect,
});
if (foundCalendarCredential) {
calendarCredential = foundCalendarCredential;
@ -725,6 +727,7 @@ async function handleSeatedEventCancellation(
where: {
id: reference.credentialId,
},
select: credentialForCalendarServiceSelect,
});
if (credential) {
@ -733,13 +736,7 @@ async function handleSeatedEventCancellation(
attendees: evt.attendees.filter((evtAttendee) => attendee.email !== evtAttendee.email),
};
if (reference.type.includes("_video")) {
integrationsToUpdate.push(
updateMeeting(
{ ...credential, appName: evt.location?.replace("integrations:", "") || "" },
updatedEvt,
reference
)
);
integrationsToUpdate.push(updateMeeting(credential, updatedEvt, reference));
}
if (reference.type.includes("_calendar")) {
const calendar = await getCalendar(credential);

View File

@ -1,4 +1,4 @@
import type { App, Attendee, Credential, EventTypeCustomInput, DestinationCalendar } from "@prisma/client";
import type { App, Attendee, DestinationCalendar, EventTypeCustomInput } from "@prisma/client";
import { Prisma } from "@prisma/client";
import async from "async";
import { isValidPhoneNumber } from "libphonenumber-js";
@ -69,6 +69,7 @@ import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import prisma, { userSelect } from "@calcom/prisma";
import type { BookingReference } from "@calcom/prisma/client";
import { BookingStatus, SchedulingType, WebhookTriggerEvents } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import {
bookingCreateBodySchemaForApi,
bookingCreateSchemaLegacyPropsForApi,
@ -85,6 +86,7 @@ import type {
IntervalLimit,
Person,
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import type { EventTypeInfo } from "../../webhooks/lib/sendPayload";
@ -111,11 +113,12 @@ interface IEventTypePaymentCredentialType {
*
* @param credential
*/
async function refreshCredential(credential: Credential): Promise<Credential> {
async function refreshCredential(credential: CredentialPayload): Promise<CredentialPayload> {
const newCredential = await prisma.credential.findUnique({
where: {
id: credential.id,
},
select: credentialForCalendarServiceSelect,
});
if (!newCredential) {
@ -130,7 +133,7 @@ async function refreshCredential(credential: Credential): Promise<Credential> {
*
* @param credentials
*/
async function refreshCredentials(credentials: Array<Credential>): Promise<Array<Credential>> {
async function refreshCredentials(credentials: Array<CredentialPayload>): Promise<Array<CredentialPayload>> {
return await async.mapLimit(credentials, 5, refreshCredential);
}
@ -139,7 +142,7 @@ async function refreshCredentials(credentials: Array<Credential>): Promise<Array
*
*/
const getAllCredentials = async (
user: User & { credentials: Credential[] },
user: User & { credentials: CredentialPayload[] },
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>>
) => {
const allCredentials = user.credentials;
@ -150,6 +153,7 @@ const getAllCredentials = async (
where: {
teamId: eventType.team.id,
},
select: credentialForCalendarServiceSelect,
});
allCredentials.push(...teamCredentialsQuery);
}
@ -165,7 +169,9 @@ const getAllCredentials = async (
},
},
select: {
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
},
});
if (teamCredentialsQuery?.credentials) {
@ -180,7 +186,9 @@ const getAllCredentials = async (
id: user.organizationId,
},
select: {
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
},
});
@ -241,7 +249,9 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
disableGuests: true,
users: {
select: {
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
...userSelect.select,
},
},
@ -317,7 +327,9 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
isFixed: true,
user: {
select: {
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
...userSelect.select,
organization: {
select: {
@ -352,7 +364,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
type IsFixedAwareUser = User & {
isFixed: boolean;
credentials: Credential[];
credentials: CredentialPayload[];
organization: { slug: string };
};
@ -736,7 +748,9 @@ async function handler(
},
select: {
...userSelect.select,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
metadata: true,
},
});
@ -786,7 +800,9 @@ async function handler(
id: eventType.userId,
},
select: {
credentials: true, // Don't leak to client
credentials: {
select: credentialForCalendarServiceSelect,
}, // Don't leak to client
...userSelect.select,
},
});
@ -995,6 +1011,7 @@ async function handler(
teamDestinationCalendars.push(user.destinationCalendar);
}
return {
id: user.id,
email: user.email ?? "",
name: user.name ?? "",
firstName: "",
@ -1112,6 +1129,7 @@ async function handler(
where: {
id: reference.credentialId,
},
select: credentialForCalendarServiceSelect,
});
if (credential) {

View File

@ -16,6 +16,7 @@ import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { getTranslation } from "@calcom/lib/server/i18n";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { CalendarEvent } from "@calcom/types/Calendar";
export const config = {
@ -74,7 +75,9 @@ export async function handlePaymentSuccess(
select: {
id: true,
username: true,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
timeZone: true,
email: true,
name: true,

View File

@ -6,7 +6,7 @@ import type Stripe from "stripe";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import EventManager from "@calcom/core/EventManager";
import dayjs from "@calcom/dayjs";
import { sendScheduledEmails, sendOrganizerRequestEmail, sendAttendeeRequestEmail } from "@calcom/emails";
import { sendAttendeeRequestEmail, sendOrganizerRequestEmail, sendScheduledEmails } from "@calcom/emails";
import { getCalEventResponses } from "@calcom/features/bookings/lib/getCalEventResponses";
import { handleConfirmation } from "@calcom/features/bookings/lib/handleConfirmation";
import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
@ -15,8 +15,9 @@ import { getErrorFromUnknown } from "@calcom/lib/errors";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import { getTranslation } from "@calcom/lib/server/i18n";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { prisma, bookingMinimalSelect } from "@calcom/prisma";
import { bookingMinimalSelect, prisma } from "@calcom/prisma";
import { BookingStatus } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent } from "@calcom/types/Calendar";
@ -164,7 +165,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
select: {
id: true,
username: true,
credentials: true,
credentials: { select: credentialForCalendarServiceSelect },
timeZone: true,
timeFormat: true,
email: true,
@ -313,7 +314,7 @@ const handleSetupSuccess = async (event: Stripe.Event) => {
name: true,
locale: true,
destinationCalendar: true,
credentials: true,
credentials: { select: credentialForCalendarServiceSelect },
},
});

View File

@ -95,7 +95,7 @@ const getDuration = (start: string, end: string): DurationObject => ({
minutes: dayjs(end).diff(dayjs(start), "minute"),
});
const getAttendees = (attendees: Person[]): Attendee[] =>
const mapAttendees = (attendees: Person[]): Attendee[] =>
attendees.map(({ email, name }) => ({ name, email, partstat: "NEEDS-ACTION" }));
export default abstract class BaseCalendarService implements Calendar {
@ -104,6 +104,7 @@ export default abstract class BaseCalendarService implements Calendar {
private headers: Record<string, string> = {};
protected integrationName = "";
private log: typeof logger;
private credential: CredentialPayload;
constructor(credential: CredentialPayload, integrationName: string, url?: string) {
this.integrationName = integrationName;
@ -118,11 +119,25 @@ export default abstract class BaseCalendarService implements Calendar {
this.credentials = { username, password };
this.headers = getBasicAuthHeaders({ username, password });
this.credential = credential;
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
}
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
private getAttendees(event: CalendarEvent) {
const attendees = mapAttendees(event.attendees);
if (event.team?.members) {
const teamAttendeesWithoutCurrentUser = event.team.members.filter(
(member) => member.email !== this.credential.user?.email
);
attendees.push(...mapAttendees(teamAttendeesWithoutCurrentUser));
}
return attendees;
}
async createEvent(event: CalendarEvent, credentialId: number): Promise<NewCalendarEventType> {
try {
const calendars = await this.listCalendars(event);
@ -138,10 +153,7 @@ export default abstract class BaseCalendarService implements Calendar {
description: getRichDescription(event),
location: getLocation(event),
organizer: { email: event.organizer.email, name: event.organizer.name },
attendees: [
...getAttendees(event.attendees),
...(event.team?.members ? getAttendees(event.team.members) : []),
],
attendees: this.getAttendees(event),
/** according to https://datatracker.ietf.org/doc/html/rfc2446#section-3.2.1, in a published iCalendar component.
* "Attendees" MUST NOT be present
* `attendees: this.getAttendees(event.attendees),`
@ -153,7 +165,10 @@ export default abstract class BaseCalendarService implements Calendar {
if (error || !iCalString)
throw new Error(`Error creating iCalString:=> ${error?.message} : ${error?.name} `);
const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
const mainHostDestinationCalendar = event.destinationCalendar
? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ??
event.destinationCalendar[0]
: undefined;
// We create the event directly on iCal
const responses = await Promise.all(
@ -214,10 +229,7 @@ export default abstract class BaseCalendarService implements Calendar {
description: getRichDescription(event),
location: getLocation(event),
organizer: { email: event.organizer.email, name: event.organizer.name },
attendees: [
...getAttendees(event.attendees),
...(event.team?.members ? getAttendees(event.team.members) : []),
],
attendees: this.getAttendees(event),
});
if (error) {

View File

@ -1,4 +1,4 @@
import type { Prisma, Credential } from "@prisma/client";
import type { Prisma } from "@prisma/client";
import { DailyLocationType } from "@calcom/app-store/locations";
import slugify from "@calcom/lib/slugify";
@ -6,6 +6,7 @@ import { PeriodType, SchedulingType } from "@calcom/prisma/enums";
import type { userSelect } from "@calcom/prisma/selects";
import type { CustomInputSchema } from "@calcom/prisma/zod-utils";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { CredentialPayload } from "@calcom/types/Credential";
type User = Prisma.UserGetPayload<typeof userSelect>;
@ -25,7 +26,7 @@ type UsernameSlugLinkProps = {
slug: string;
};
const user: User & { credentials: Credential[] } = {
const user: User & { credentials: CredentialPayload[] } = {
metadata: null,
theme: null,
credentials: [],

View File

@ -1,19 +1,12 @@
import { prisma } from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
export async function getUsersCredentials(userId: number) {
const credentials = await prisma.credential.findMany({
where: {
userId,
},
select: {
id: true,
type: true,
key: true,
userId: true,
appId: true,
invalid: true,
teamId: true,
},
select: credentialForCalendarServiceSelect,
orderBy: {
id: "asc",
},

View File

@ -1,11 +1,31 @@
import { Prisma } from "@prisma/client";
export const credentialForCalendarServiceSelect = Prisma.validator<Prisma.CredentialSelect>()({
id: true,
appId: true,
type: true,
userId: true,
user: {
select: {
email: true,
},
},
teamId: true,
key: true,
invalid: true,
});
export const safeCredentialSelect = Prisma.validator<Prisma.CredentialSelect>()({
id: true,
type: true,
/** Omitting to avoid frontend leaks */
// key: true,
userId: true,
user: {
select: {
email: true,
},
},
teamId: true,
appId: true,
invalid: true,

View File

@ -3,6 +3,7 @@ import type { DestinationCalendar } from "@prisma/client";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { prisma } from "@calcom/prisma";
import { AppCategories } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import type { TConnectedCalendarsInputSchema } from "./connectedCalendars.schema";
@ -26,6 +27,7 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
enabled: true,
},
},
select: credentialForCalendarServiceSelect,
});
// get user's credentials + their connected integrations

View File

@ -10,9 +10,9 @@ import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { deletePayment } from "@calcom/lib/payment/deletePayment";
import { getTranslation } from "@calcom/lib/server/i18n";
import { bookingMinimalSelect } from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import { bookingMinimalSelect, prisma } from "@calcom/prisma";
import { AppCategories, BookingStatus } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
@ -37,8 +37,7 @@ export const deleteCredentialHandler = async ({ ctx, input }: DeleteCredentialOp
...(teamId ? { teamId } : { userId: ctx.user.id }),
},
select: {
key: true,
appId: true,
...credentialForCalendarServiceSelect,
app: {
select: {
slug: true,
@ -46,11 +45,6 @@ export const deleteCredentialHandler = async ({ ctx, input }: DeleteCredentialOp
dirName: true,
},
},
id: true,
type: true,
userId: true,
teamId: true,
invalid: true,
},
});

View File

@ -1,4 +1,4 @@
import type { Credential, Prisma } from "@prisma/client";
import type { Prisma } from "@prisma/client";
import type { CredentialOwner } from "@calcom/app-store/types";
import getEnabledAppsFromCredentials from "@calcom/lib/apps/getEnabledAppsFromCredentials";
@ -6,7 +6,9 @@ import getInstallCountPerApp from "@calcom/lib/apps/getInstallCountPerApp";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { TIntegrationsInputSchema } from "./integrations.schema";
@ -20,7 +22,9 @@ type IntegrationsOptions = {
type TeamQuery = Prisma.TeamGetPayload<{
select: {
id: true;
credentials?: true;
credentials: {
select: typeof import("@calcom/prisma/selects/credential").credentialForCalendarServiceSelect;
};
name: true;
logo: true;
members: {
@ -63,7 +67,9 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
},
select: {
id: true,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
name: true,
logo: true,
members: {
@ -77,7 +83,9 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
parent: {
select: {
id: true,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
name: true,
logo: true,
members: {
@ -109,7 +117,7 @@ export const integrationsHandler = async ({ ctx, input }: IntegrationsOptions) =
userTeams = [...teamsQuery, ...parentTeams];
const teamAppCredentials: Credential[] = userTeams.flatMap((teamApp) => {
const teamAppCredentials: CredentialPayload[] = userTeams.flatMap((teamApp) => {
return teamApp.credentials ? teamApp.credentials.flat() : [];
});
if (!includeTeamInstalledApps || teamId) {

View File

@ -6,6 +6,7 @@ import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server";
import { getUsersCredentials } from "@calcom/lib/server/getUsersCredentials";
import { prisma } from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
@ -46,6 +47,7 @@ export const editLocationHandler = async ({ ctx, input }: EditLocationOptions) =
where: {
id: details.credentialId,
},
select: credentialForCalendarServiceSelect,
});
}

View File

@ -16,6 +16,7 @@ import getSlots from "@calcom/lib/slots";
import prisma, { availabilityUserSelect } from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import type { EventBusyDate } from "@calcom/types/Calendar";
@ -177,7 +178,7 @@ export async function getEventType(
isFixed: true,
user: {
select: {
credentials: true,
credentials: { select: credentialForCalendarServiceSelect },
...availabilityUserSelect,
},
},
@ -185,7 +186,7 @@ export async function getEventType(
},
users: {
select: {
credentials: true,
credentials: { select: credentialForCalendarServiceSelect },
...availabilityUserSelect,
},
},
@ -223,7 +224,9 @@ export async function getDynamicEventType(input: TGetScheduleInputSchema) {
select: {
allowDynamicBooking: true,
...availabilityUserSelect,
credentials: true,
credentials: {
select: credentialForCalendarServiceSelect,
},
},
});
const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking);

View File

@ -2,6 +2,7 @@ import { getUserAvailability } from "@calcom/core/getUserAvailability";
import { isTeamMember } from "@calcom/lib/server/queries/teams";
import { availabilityUserSelect } from "@calcom/prisma";
import { prisma } from "@calcom/prisma";
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
import { TRPCError } from "@trpc/server";
@ -25,7 +26,9 @@ export const getMemberAvailabilityHandler = async ({ ctx, input }: GetMemberAvai
include: {
user: {
select: {
credentials: true, // needed for getUserAvailability
credentials: {
select: credentialForCalendarServiceSelect,
}, // needed for getUserAvailability
...availabilityUserSelect,
organization: {
select: {

View File

@ -1,4 +1,4 @@
import type { Prisma, DestinationCalendar, SelectedCalendar, BookingSeat } from "@prisma/client";
import type { BookingSeat, DestinationCalendar, Prisma, SelectedCalendar } from "@prisma/client";
import type { Dayjs } from "dayjs";
import type { calendar_v3 } from "googleapis";
import type { Time } from "ical.js";
@ -39,6 +39,7 @@ export type Person = {
};
export type TeamMember = {
id?: number;
name: string;
email: string;
timeZone: string;

View File

@ -6,20 +6,10 @@ import type { Prisma } from "@prisma/client";
* Also there may be a better place to save this.
*/
export type CredentialPayload = Prisma.CredentialGetPayload<{
select: {
id: true;
appId: true;
type: true;
userId: true;
teamId: true;
key: true;
invalid: true;
};
select: typeof import("@calcom/prisma/selects/credential").credentialForCalendarServiceSelect;
}>;
export type CredentialFrontendPayload = Omit<CredentialPayload, "key"> & {
/** We should type error if keys are leaked to the frontend */
key?: never;
};
export type CredentialWithAppName = CredentialPayload & { appName: string };