cal.pub0.org/lib/integrations/calendar/CalendarManager.ts

181 lines
5.4 KiB
TypeScript

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<string, CalendarServiceType> = {
[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<Omit<Credential, "userId">>, 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<typeof getCalendarCredentials>,
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<EventResult> => {
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<EventResult> => {
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<unknown> => {
const calendar = getCalendar(credential);
if (calendar) {
return calendar.deleteEvent(uid);
}
return Promise.resolve({});
};