cal.pub0.org/packages/app-store/exchange2013calendar/lib/CalendarService.ts

237 lines
7.9 KiB
TypeScript

import {
Appointment,
Attendee,
CalendarView,
ConflictResolutionMode,
DateTime,
DeleteMode,
ExchangeService,
ExchangeVersion,
FolderId,
FolderView,
ItemId,
LegacyFreeBusyStatus,
MessageBody,
PropertySet,
SendInvitationsMode,
SendInvitationsOrCancellationsMode,
Uri,
WebCredentials,
WellKnownFolderName,
} from "ews-javascript-api";
import { symmetricDecrypt } from "@calcom/lib/crypto";
// Probably don't need
// import { CALENDAR_INTEGRATIONS_TYPES } from "@calcom/lib/integrations/calendar/constants/generals";
import logger from "@calcom/lib/logger";
import type {
Calendar,
CalendarEvent,
EventBusyDate,
IntegrationCalendar,
NewCalendarEventType,
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
export default class ExchangeCalendarService implements Calendar {
private url = "";
private integrationName = "";
private log: typeof logger;
private readonly exchangeVersion: ExchangeVersion;
private credentials: Record<string, string>;
constructor(credential: CredentialPayload) {
this.integrationName = "exchange2013_calendar";
this.log = logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
const decryptedCredential = JSON.parse(
symmetricDecrypt(credential.key?.toString() || "", process.env.CALENDSO_ENCRYPTION_KEY || "")
);
const username = decryptedCredential.username;
const url = decryptedCredential.url;
const password = decryptedCredential.password;
this.url = url;
this.credentials = {
username,
password,
};
this.exchangeVersion = ExchangeVersion.Exchange2013;
}
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
try {
const appointment = new Appointment(this.getExchangeService()); // service instance of ExchangeService
appointment.Subject = event.title;
appointment.Start = DateTime.Parse(event.startTime); // moment string
appointment.End = DateTime.Parse(event.endTime); // moment string
appointment.Location = event.location || "Location not defined!";
appointment.Body = new MessageBody(event.description || ""); // you can not use any special character or escape the content
for (let i = 0; i < event.attendees.length; i++) {
appointment.RequiredAttendees.Add(new Attendee(event.attendees[i].email));
}
if (event.team?.members) {
event.team.members.forEach((member) => {
appointment.RequiredAttendees.Add(new Attendee(member.email));
});
}
await appointment.Save(SendInvitationsMode.SendToAllAndSaveCopy);
return {
uid: appointment.Id.UniqueId,
id: appointment.Id.UniqueId,
password: "",
type: "",
url: "",
additionalInfo: {},
};
} catch (reason) {
this.log.error(reason);
throw reason;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
try {
const appointment = await Appointment.Bind(
this.getExchangeService(),
new ItemId(uid),
new PropertySet()
);
appointment.Subject = event.title;
appointment.Start = DateTime.Parse(event.startTime); // moment string
appointment.End = DateTime.Parse(event.endTime); // moment string
appointment.Location = event.location || "Location not defined!";
appointment.Body = new MessageBody(event.description || ""); // you can not use any special character or escape the content
for (let i = 0; i < event.attendees.length; i++) {
appointment.RequiredAttendees.Add(new Attendee(event.attendees[i].email));
}
if (event.team?.members) {
event.team.members.forEach((member) => {
appointment.RequiredAttendees.Add(new Attendee(member.email));
});
}
appointment.Update(
ConflictResolutionMode.AlwaysOverwrite,
SendInvitationsOrCancellationsMode.SendToAllAndSaveCopy
);
} catch (reason) {
this.log.error(reason);
throw reason;
}
}
async deleteEvent(uid: string) {
try {
const appointment = await Appointment.Bind(
this.getExchangeService(),
new ItemId(uid),
new PropertySet()
);
// Delete the appointment. Note that the item ID will change when the item is moved to the Deleted Items folder.
appointment.Delete(DeleteMode.MoveToDeletedItems);
} catch (reason) {
this.log.error(reason);
throw reason;
}
}
async getAvailability(dateFrom: string, dateTo: string, selectedCalendars: IntegrationCalendar[]) {
try {
const externalCalendars = await this.listCalendars();
const calendarsToGetAppointmentsFrom = [];
for (let i = 0; i < selectedCalendars.length; i++) {
//Only select vaild calendars! (We get all all active calendars on the instance! even from different users!)
for (let k = 0; k < externalCalendars.length; k++) {
if (selectedCalendars[i].externalId == externalCalendars[k].externalId) {
calendarsToGetAppointmentsFrom.push(selectedCalendars[i]);
}
}
}
const finaleRet = [];
for (let i = 0; i < calendarsToGetAppointmentsFrom.length; i++) {
const calendarFolderId = new FolderId(calendarsToGetAppointmentsFrom[i].externalId);
const localReturn = await this.getExchangeService()
.FindAppointments(
calendarFolderId,
new CalendarView(DateTime.Parse(dateFrom), DateTime.Parse(dateTo))
)
.then(function (params) {
const ret: EventBusyDate[] = [];
for (let k = 0; k < params.Items.length; k++) {
if (params.Items[k].LegacyFreeBusyStatus != LegacyFreeBusyStatus.Free) {
//Dont use this appointment if "ShowAs" was set to "free"
ret.push({
start: new Date(params.Items[k].Start.ToISOString()),
end: new Date(params.Items[k].End.ToISOString()),
});
}
}
return ret;
});
finaleRet.push(...localReturn);
}
return finaleRet;
} catch (reason) {
this.log.error(reason);
throw reason;
}
}
async listCalendars() {
try {
const allFolders: IntegrationCalendar[] = [];
return this.getExchangeService()
.FindFolders(WellKnownFolderName.MsgFolderRoot, new FolderView(1000))
.then(async (res) => {
for (const k in res.Folders) {
const f = res.Folders[k];
if (f.FolderClass == "IPF.Appointment") {
//Select parent folder for all calendars
allFolders.push({
externalId: f.Id.UniqueId,
name: f.DisplayName ?? "",
primary: true, //The first one is prime
integration: this.integrationName,
});
return await this.getExchangeService()
.FindFolders(f.Id, new FolderView(1000))
.then((res) => {
//Find all calendars inside calendar folder
res.Folders.forEach((fs) => {
allFolders.push({
externalId: fs.Id.UniqueId,
name: fs.DisplayName ?? "",
primary: false,
integration: this.integrationName,
});
});
return allFolders;
});
}
}
return allFolders;
});
} catch (reason) {
this.log.error(reason);
throw reason;
}
}
private getExchangeService(): ExchangeService {
const exch1 = new ExchangeService(this.exchangeVersion);
exch1.Credentials = new WebCredentials(this.credentials.username, this.credentials.password);
exch1.Url = new Uri(this.url);
return exch1;
}
}