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>pull/11369/head
parent
add297b09a
commit
197b435b6f
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" });
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: "",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Reference in New Issue