2022-06-13 22:12:27 +00:00
|
|
|
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";
|
2023-02-16 22:39:57 +00:00
|
|
|
import type {
|
2022-06-17 18:34:41 +00:00
|
|
|
Calendar,
|
|
|
|
CalendarEvent,
|
|
|
|
EventBusyDate,
|
|
|
|
IntegrationCalendar,
|
|
|
|
NewCalendarEventType,
|
|
|
|
} from "@calcom/types/Calendar";
|
2023-02-16 22:39:57 +00:00
|
|
|
import type { CredentialPayload } from "@calcom/types/Credential";
|
2022-06-13 22:12:27 +00:00
|
|
|
|
|
|
|
export default class ExchangeCalendarService implements Calendar {
|
|
|
|
private url = "";
|
|
|
|
private integrationName = "";
|
|
|
|
private log: typeof logger;
|
|
|
|
private readonly exchangeVersion: ExchangeVersion;
|
|
|
|
private credentials: Record<string, string>;
|
|
|
|
|
2022-10-31 22:06:03 +00:00
|
|
|
constructor(credential: CredentialPayload) {
|
2022-06-13 22:12:27 +00:00
|
|
|
this.integrationName = "exchange2013_calendar";
|
|
|
|
|
|
|
|
this.log = logger.getChildLogger({ 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;
|
|
|
|
}
|
|
|
|
|
2022-06-17 18:34:41 +00:00
|
|
|
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
|
2022-06-13 22:12:27 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-03-21 03:15:59 +00:00
|
|
|
if (event.team?.members) {
|
|
|
|
event.team.members.forEach((member) => {
|
|
|
|
appointment.RequiredAttendees.Add(new Attendee(member.email));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-06-13 22:12:27 +00:00
|
|
|
await appointment.Save(SendInvitationsMode.SendToAllAndSaveCopy);
|
|
|
|
|
|
|
|
return {
|
|
|
|
uid: appointment.Id.UniqueId,
|
|
|
|
id: appointment.Id.UniqueId,
|
|
|
|
password: "",
|
|
|
|
type: "",
|
|
|
|
url: "",
|
2022-06-17 18:34:41 +00:00
|
|
|
additionalInfo: {},
|
2022-06-13 22:12:27 +00:00
|
|
|
};
|
|
|
|
} catch (reason) {
|
|
|
|
this.log.error(reason);
|
|
|
|
throw reason;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-03 15:35:08 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
2022-06-17 18:34:41 +00:00
|
|
|
async updateEvent(uid: string, event: CalendarEvent): Promise<any> {
|
2022-06-13 22:12:27 +00:00
|
|
|
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));
|
|
|
|
}
|
2023-03-21 03:15:59 +00:00
|
|
|
if (event.team?.members) {
|
|
|
|
event.team.members.forEach((member) => {
|
|
|
|
appointment.RequiredAttendees.Add(new Attendee(member.email));
|
|
|
|
});
|
|
|
|
}
|
2022-06-13 22:12:27 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|