import { Credential, SelectedCalendar } from "@prisma/client"; import _ from "lodash"; import { getUid } from "@lib/CalEventParser"; import { getErrorFromUnknown } from "@lib/errors"; import { EventResult } from "@lib/events/EventManager"; import logger from "@lib/logger"; import notEmpty from "@lib/notEmpty"; import { ALL_INTEGRATIONS } from "../getIntegrations"; import { CALENDAR_INTEGRATIONS_TYPES } from "./constants/generals"; import { CalendarServiceType, EventBusyDate } from "./constants/types"; import { Calendar, CalendarEvent } from "./interfaces/Calendar"; import AppleCalendarService from "./services/AppleCalendarService"; import CalDavCalendarService from "./services/CalDavCalendarService"; import GoogleCalendarService from "./services/GoogleCalendarService"; import Office365CalendarService from "./services/Office365CalendarService"; const CALENDARS: Record = { [CALENDAR_INTEGRATIONS_TYPES.apple]: AppleCalendarService, [CALENDAR_INTEGRATIONS_TYPES.caldav]: CalDavCalendarService, [CALENDAR_INTEGRATIONS_TYPES.google]: GoogleCalendarService, [CALENDAR_INTEGRATIONS_TYPES.office365]: Office365CalendarService, }; const log = logger.getChildLogger({ prefix: ["CalendarManager"] }); export const getCalendar = (credential: Credential): Calendar | null => { const { type: calendarType } = credential; const calendar = CALENDARS[calendarType]; if (!calendar) { log.warn(`calendar of type ${calendarType} does not implemented`); return null; } return new calendar(credential); }; export const getCalendarCredentials = (credentials: Array>, userId: number) => { const calendarCredentials = credentials .filter((credential) => credential.type.endsWith("_calendar")) .flatMap((credential) => { const integration = ALL_INTEGRATIONS.find((integration) => integration.type === credential.type); const calendar = getCalendar({ ...credential, userId, }); return integration && calendar && integration.variant === "calendar" ? [{ integration, credential, calendar }] : []; }); return calendarCredentials; }; export const getConnectedCalendars = async ( calendarCredentials: ReturnType, selectedCalendars: { externalId: string }[] ) => { const connectedCalendars = await Promise.all( calendarCredentials.map(async (item) => { const { calendar, integration, credential } = item; const credentialId = credential.id; try { const cals = await calendar.listCalendars(); const calendars = _(cals) .map((cal) => ({ ...cal, primary: cal.primary || null, isSelected: selectedCalendars.some((selected) => selected.externalId === cal.externalId), })) .sortBy(["primary"]) .value(); const primary = calendars.find((item) => item.primary) ?? calendars[0]; if (!primary) { throw new Error("No primary calendar found"); } return { integration, credentialId, primary, calendars, }; } catch (_error) { const error = getErrorFromUnknown(_error); return { integration, credentialId, error: { message: error.message, }, }; } }) ); return connectedCalendars; }; export const getBusyCalendarTimes = async ( withCredentials: Credential[], dateFrom: string, dateTo: string, selectedCalendars: SelectedCalendar[] ) => { const calendars = withCredentials .filter((credential) => credential.type.endsWith("_calendar")) .map((credential) => getCalendar(credential)) .filter(notEmpty); let results: EventBusyDate[][] = []; try { results = await Promise.all(calendars.map((c) => c.getAvailability(dateFrom, dateTo, selectedCalendars))); } catch (error) { log.warn(error); } return results.reduce((acc, availability) => acc.concat(availability), []); }; export const createEvent = async (credential: Credential, calEvent: CalendarEvent): Promise => { const uid: string = getUid(calEvent); const calendar = getCalendar(credential); let success = true; const creationResult = calendar ? await calendar.createEvent(calEvent).catch((e) => { log.error("createEvent failed", e, calEvent); success = false; return undefined; }) : undefined; return { type: credential.type, success, uid, createdEvent: creationResult, originalEvent: calEvent, }; }; export const updateEvent = async ( credential: Credential, calEvent: CalendarEvent, bookingRefUid: string | null ): Promise => { const uid = getUid(calEvent); const calendar = getCalendar(credential); let success = true; const updatedResult = calendar && bookingRefUid ? await calendar.updateEvent(bookingRefUid, calEvent).catch((e) => { log.error("updateEvent failed", e, calEvent); success = false; return undefined; }) : undefined; return { type: credential.type, success, uid, updatedEvent: updatedResult, originalEvent: calEvent, }; }; export const deleteEvent = (credential: Credential, uid: string): Promise => { const calendar = getCalendar(credential); if (calendar) { return calendar.deleteEvent(uid); } return Promise.resolve({}); };