@openinc/parse-server-opendash 4.0.28 → 4.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/features/openservice/schedules/calendarSync/functions/eventToSchedule.d.ts +11 -5
- package/dist/features/openservice/schedules/calendarSync/functions/eventToSchedule.js +62 -23
- package/dist/features/openservice/schedules/calendarSync/functions/scheduleToEvent.d.ts +2 -1
- package/dist/features/openservice/schedules/calendarSync/functions/scheduleToEvent.js +62 -33
- package/dist/features/openservice/schedules/calendarSync/service/CalendarManager.d.ts +27 -22
- package/dist/features/openservice/schedules/calendarSync/service/CalendarManager.js +101 -48
- package/dist/features/openservice/schedules/calendarSync/service/CalendarSyncPoller.d.ts +20 -0
- package/dist/features/openservice/schedules/calendarSync/service/CalendarSyncPoller.js +197 -0
- package/dist/features/openservice/schedules/calendarSync/service/calendarMetaHelper.d.ts +36 -0
- package/dist/features/openservice/schedules/calendarSync/service/calendarMetaHelper.js +89 -0
- package/dist/features/openservice/schedules/calendarSync/types/ConfigValues.d.ts +8 -0
- package/dist/features/openservice/schedules/calendarSync/types/ConfigValues.js +2 -0
- package/dist/features/openservice/schedules/calendarSync/types/Event.d.ts +1 -1
- package/dist/features/openservice/schedules/initSchedulesFeature.js +6 -0
- package/dist/hooks/Config_open_service.js +2 -0
- package/dist/hooks/Maintenance_Schedule.js +47 -22
- package/dist/types/Meta_Config.d.ts +4 -3
- package/dist/types/Meta_Entry.d.ts +4 -3
- package/dist/types/custom/MetaField.d.ts +117 -0
- package/dist/types/custom/MetaField.js +21 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +4 -2
- package/package.json +5 -5
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { GraphEventResponse } from "../types/Event.js";
|
|
2
2
|
export type ScheduleCronTimestamp = {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
startDate: Date;
|
|
4
|
+
endDate?: Date;
|
|
5
5
|
number: number;
|
|
6
6
|
unit: "days" | "weeks" | "months";
|
|
7
7
|
};
|
|
8
|
-
export
|
|
9
|
-
|
|
8
|
+
export type NotifyBeforeDue = {
|
|
9
|
+
value: number;
|
|
10
|
+
unit: "minutes" | "hours" | "days" | "weeks";
|
|
11
|
+
};
|
|
12
|
+
export type EventToScheduleResult = {
|
|
10
13
|
schedule_type: "timestamp";
|
|
11
|
-
|
|
14
|
+
timestamp: ScheduleCronTimestamp;
|
|
15
|
+
notifyBeforeDue?: NotifyBeforeDue;
|
|
16
|
+
};
|
|
17
|
+
export declare function eventToSchedule(event: GraphEventResponse): EventToScheduleResult | undefined;
|
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.eventToSchedule = eventToSchedule;
|
|
7
|
+
const dayjs_1 = __importDefault(require("dayjs"));
|
|
8
|
+
// Parses only the date portion of an Outlook dateTime string to avoid
|
|
9
|
+
// timezone misinterpretation (Outlook dateTime is local to the event's
|
|
10
|
+
// timezone, not UTC).
|
|
11
|
+
function parseDateOnly(dateTime) {
|
|
12
|
+
return (0, dayjs_1.default)(dateTime.slice(0, 10));
|
|
13
|
+
}
|
|
14
|
+
function minutesToNotifyBeforeDue(minutes) {
|
|
15
|
+
if (minutes % (60 * 24 * 7) === 0)
|
|
16
|
+
return { value: minutes / (60 * 24 * 7), unit: "weeks" };
|
|
17
|
+
if (minutes % (60 * 24) === 0)
|
|
18
|
+
return { value: minutes / (60 * 24), unit: "days" };
|
|
19
|
+
if (minutes % 60 === 0)
|
|
20
|
+
return { value: minutes / 60, unit: "hours" };
|
|
21
|
+
return { value: minutes, unit: "minutes" };
|
|
22
|
+
}
|
|
4
23
|
function eventToSchedule(event) {
|
|
24
|
+
const notifyBeforeDue = event.isReminderOn && typeof event.reminderMinutesBeforeStart === "number"
|
|
25
|
+
? minutesToNotifyBeforeDue(event.reminderMinutesBeforeStart)
|
|
26
|
+
: undefined;
|
|
5
27
|
const recurrence = event?.recurrence;
|
|
6
|
-
|
|
7
|
-
|
|
28
|
+
// Single (non-recurring) event: treat as one-day schedule regardless of
|
|
29
|
+
// the event's time or duration — our schedules are always all-day.
|
|
30
|
+
if (!recurrence) {
|
|
31
|
+
if (!event.start?.dateTime)
|
|
32
|
+
return undefined;
|
|
33
|
+
const date = parseDateOnly(event.start.dateTime).toDate();
|
|
34
|
+
return {
|
|
35
|
+
schedule_type: "timestamp",
|
|
36
|
+
timestamp: {
|
|
37
|
+
startDate: date,
|
|
38
|
+
endDate: date,
|
|
39
|
+
number: 1,
|
|
40
|
+
unit: "days",
|
|
41
|
+
},
|
|
42
|
+
notifyBeforeDue,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
8
45
|
const pattern = recurrence.pattern;
|
|
9
46
|
const range = recurrence.range;
|
|
10
47
|
if (!pattern || !range || !pattern.type)
|
|
@@ -21,39 +58,41 @@ function eventToSchedule(event) {
|
|
|
21
58
|
case "relativeMonthly":
|
|
22
59
|
unit = "months";
|
|
23
60
|
break;
|
|
61
|
+
case "absoluteYearly":
|
|
62
|
+
case "relativeYearly":
|
|
63
|
+
// Map yearly recurrence to a 12-month interval
|
|
64
|
+
unit = "months";
|
|
65
|
+
break;
|
|
24
66
|
default:
|
|
25
|
-
// unsupported recurrence type
|
|
26
67
|
return undefined;
|
|
27
68
|
}
|
|
28
69
|
const interval = typeof pattern.interval === "number" && pattern.interval > 0
|
|
29
70
|
? pattern.interval
|
|
30
71
|
: 1;
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
72
|
+
// Yearly patterns don't carry an interval in Outlook (always 1 year)
|
|
73
|
+
const effectiveInterval = pattern.type === "absoluteYearly" || pattern.type === "relativeYearly"
|
|
74
|
+
? 12
|
|
75
|
+
: interval;
|
|
76
|
+
// range.startDate is always YYYY-MM-DD
|
|
77
|
+
const startDate = parseDateOnly(range.startDate);
|
|
78
|
+
let endDate;
|
|
79
|
+
if (range.type === "endDate") {
|
|
80
|
+
endDate = parseDateOnly(range.endDate);
|
|
39
81
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
82
|
+
else if (range.type === "numbered") {
|
|
83
|
+
// Compute end date from occurrence count
|
|
84
|
+
const occurrences = range.numberOfOccurrences ?? 1;
|
|
85
|
+
endDate = startDate.add((occurrences - 1) * effectiveInterval, unit);
|
|
44
86
|
}
|
|
45
|
-
|
|
46
|
-
endData = new Date(event.end.dateTime);
|
|
47
|
-
}
|
|
48
|
-
if (!startData)
|
|
49
|
-
return undefined;
|
|
87
|
+
// range.type === "noEnd" → endDate stays undefined
|
|
50
88
|
return {
|
|
51
89
|
schedule_type: "timestamp",
|
|
52
90
|
timestamp: {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
number:
|
|
91
|
+
startDate: startDate.toDate(),
|
|
92
|
+
endDate: endDate?.toDate(),
|
|
93
|
+
number: effectiveInterval,
|
|
56
94
|
unit,
|
|
57
95
|
},
|
|
96
|
+
notifyBeforeDue,
|
|
58
97
|
};
|
|
59
98
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Maintenance_Schedule } from "../../../../../types/index.js";
|
|
2
|
+
import { CalendarEventSnapshot } from "../service/calendarMetaHelper.js";
|
|
2
3
|
import { GraphEvent } from "../types/Event.js";
|
|
3
|
-
export declare function scheduleToEvent(schedule: Maintenance_Schedule): Promise<GraphEvent | undefined>;
|
|
4
|
+
export declare function scheduleToEvent(schedule: Maintenance_Schedule, originalEvent?: CalendarEventSnapshot): Promise<GraphEvent | undefined>;
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.scheduleToEvent = scheduleToEvent;
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
const dayjs_1 = __importDefault(require("dayjs"));
|
|
8
|
+
function notifyBeforeDueToMinutes(notify) {
|
|
9
|
+
switch (notify.unit) {
|
|
10
|
+
case "weeks":
|
|
11
|
+
return notify.value * 60 * 24 * 7;
|
|
12
|
+
case "days":
|
|
13
|
+
return notify.value * 60 * 24;
|
|
14
|
+
case "hours":
|
|
15
|
+
return notify.value * 60;
|
|
16
|
+
case "minutes":
|
|
17
|
+
return notify.value;
|
|
18
|
+
case "seconds":
|
|
19
|
+
return Math.round(notify.value / 60);
|
|
20
|
+
case "milliseconds":
|
|
21
|
+
return Math.round(notify.value / 60000);
|
|
22
|
+
default:
|
|
23
|
+
return notify.value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function scheduleToEvent(schedule, originalEvent) {
|
|
6
27
|
try {
|
|
7
|
-
await schedule.fetchWithInclude("template"
|
|
28
|
+
await schedule.fetchWithInclude(["template"], {
|
|
29
|
+
useMasterKey: true,
|
|
30
|
+
});
|
|
8
31
|
}
|
|
9
32
|
catch (error) {
|
|
10
|
-
console.error(
|
|
33
|
+
console.error(`[scheduleToEvent] Failed to fetch template for schedule ${schedule.id}:`, error);
|
|
11
34
|
}
|
|
12
35
|
const template = schedule.get("template");
|
|
13
36
|
const templateCron = template?.get("cron");
|
|
@@ -16,33 +39,39 @@ async function scheduleToEvent(schedule) {
|
|
|
16
39
|
const end = scheduleCron?.timestamp?.endDate ?? templateCron?.timestamp?.endDate;
|
|
17
40
|
const interval = scheduleCron?.timestamp?.number ?? templateCron?.timestamp?.number;
|
|
18
41
|
const unit = scheduleCron?.timestamp?.unit ?? templateCron?.timestamp?.unit;
|
|
19
|
-
if (!start || !
|
|
20
|
-
console.log("Insufficient data to create event from schedule.");
|
|
42
|
+
if (!start || !interval || !unit) {
|
|
21
43
|
return;
|
|
22
44
|
}
|
|
45
|
+
const description = schedule.get("description");
|
|
46
|
+
const notifyBeforeDue = schedule.get("notifyBeforeDue");
|
|
47
|
+
// If the original Outlook event had specific times (not all-day), preserve
|
|
48
|
+
// them so we don't silently convert a timed event to all-day on update.
|
|
49
|
+
const preserveTimes = originalEvent != null && !originalEvent.isAllDay;
|
|
50
|
+
const toDateOnly = (d) => d.toISOString().slice(0, 10);
|
|
23
51
|
const event = {
|
|
24
52
|
subject: schedule.get("title"),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
timeZone: "UTC",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
body: { contentType: "Text", content: description ?? "" },
|
|
54
|
+
start: preserveTimes
|
|
55
|
+
? originalEvent.start
|
|
56
|
+
: { dateTime: toDateOnly(start), timeZone: "UTC" },
|
|
57
|
+
end: preserveTimes
|
|
58
|
+
? originalEvent.end
|
|
59
|
+
: {
|
|
60
|
+
dateTime: toDateOnly((0, dayjs_1.default)(start).add(1, "day").toDate()),
|
|
61
|
+
timeZone: "UTC",
|
|
62
|
+
},
|
|
63
|
+
isAllDay: preserveTimes ? undefined : true,
|
|
64
|
+
isReminderOn: notifyBeforeDue != null,
|
|
65
|
+
reminderMinutesBeforeStart: notifyBeforeDue
|
|
66
|
+
? notifyBeforeDueToMinutes(notifyBeforeDue)
|
|
67
|
+
: undefined,
|
|
35
68
|
};
|
|
36
|
-
// Helper to format date as YYYY-MM-DD for recurrence range
|
|
37
|
-
const toDateOnly = (d) => d.toISOString().slice(0, 10);
|
|
38
|
-
// determine pattern type and additional fields
|
|
39
69
|
let pattern = { interval };
|
|
40
70
|
if (unit === "days") {
|
|
41
71
|
pattern.type = "daily";
|
|
42
72
|
}
|
|
43
73
|
else if (unit === "weeks") {
|
|
44
74
|
pattern.type = "weekly";
|
|
45
|
-
// include the weekday of the start date so weekly recurrences occur on that weekday
|
|
46
75
|
const dayNames = [
|
|
47
76
|
"Sunday",
|
|
48
77
|
"Monday",
|
|
@@ -57,32 +86,32 @@ async function scheduleToEvent(schedule) {
|
|
|
57
86
|
}
|
|
58
87
|
else if (unit === "months") {
|
|
59
88
|
pattern.type = "absoluteMonthly";
|
|
60
|
-
// Graph uses dayOfMonth for absoluteMonthly
|
|
61
89
|
pattern.dayOfMonth = start.getUTCDate();
|
|
62
90
|
}
|
|
63
91
|
else {
|
|
64
|
-
// unsupported unit -> skip recurrence
|
|
65
92
|
pattern = null;
|
|
66
93
|
}
|
|
67
94
|
if (pattern) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
range: {
|
|
95
|
+
const range = end
|
|
96
|
+
? {
|
|
71
97
|
type: "endDate",
|
|
72
98
|
startDate: toDateOnly(start),
|
|
73
99
|
endDate: toDateOnly(end),
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
}
|
|
101
|
+
: {
|
|
102
|
+
type: "noEnd",
|
|
103
|
+
startDate: toDateOnly(start),
|
|
104
|
+
};
|
|
105
|
+
event.recurrence = { pattern, range };
|
|
106
|
+
// All-day events need date-only strings for start/end in the Graph payload.
|
|
107
|
+
// Timed events already have their original times set above — don't touch them.
|
|
108
|
+
if (!preserveTimes) {
|
|
78
109
|
event.start.dateTime = toDateOnly(start);
|
|
79
|
-
// Graph's endDate in event object is exclusive for all-day events; keep as provided for recurrence range
|
|
80
|
-
// Set end to next day to represent an all-day event that begins and ends the same day
|
|
81
110
|
const endForEvent = new Date(start);
|
|
82
111
|
endForEvent.setUTCDate(endForEvent.getUTCDate() + 1);
|
|
83
|
-
event.end
|
|
112
|
+
if (event.end)
|
|
113
|
+
event.end.dateTime = toDateOnly(endForEvent);
|
|
84
114
|
}
|
|
85
115
|
}
|
|
86
|
-
console.log("Schedule converted to Event:", event);
|
|
87
116
|
return event;
|
|
88
117
|
}
|
|
@@ -3,32 +3,37 @@ import { GraphEvent, GraphEventResponse } from "../types/Event.js";
|
|
|
3
3
|
* Manager for calendar operations via Microsoft Graph API
|
|
4
4
|
*/
|
|
5
5
|
export declare class CalendarManager {
|
|
6
|
-
|
|
6
|
+
tenantID?: string;
|
|
7
|
+
clientID?: string;
|
|
8
|
+
clientSecret?: string;
|
|
9
|
+
userMail?: string;
|
|
10
|
+
constructor(config: {
|
|
11
|
+
tenantId: string;
|
|
12
|
+
clientID: string;
|
|
13
|
+
clientSecret: string;
|
|
14
|
+
userMail: string;
|
|
15
|
+
});
|
|
16
|
+
get isValid(): true | string;
|
|
7
17
|
private getAccess;
|
|
8
|
-
/**
|
|
9
|
-
* Create a new event in the calendar
|
|
10
|
-
* @param event The event data to create
|
|
11
|
-
* @returns the created event response
|
|
12
|
-
*/
|
|
13
18
|
createEvent(event: GraphEvent): Promise<GraphEventResponse | undefined>;
|
|
14
|
-
|
|
15
|
-
* Update an event in the calendar
|
|
16
|
-
* @param eventId The ID of the event to update
|
|
17
|
-
* @param event The updated event data
|
|
18
|
-
* @returns
|
|
19
|
-
*/
|
|
20
|
-
updateEvent(eventId: string, event: Partial<GraphEvent>): Promise<unknown>;
|
|
21
|
-
/**
|
|
22
|
-
* Delete an event from the calendar
|
|
23
|
-
* @param eventId The ID of the event to delete
|
|
24
|
-
* @returns whether the deletion was successful
|
|
25
|
-
*/
|
|
19
|
+
updateEvent(eventId: string, event: Partial<GraphEvent>): Promise<GraphEventResponse | undefined>;
|
|
26
20
|
deleteEvent(eventId: string): Promise<boolean | undefined>;
|
|
27
|
-
|
|
28
|
-
* Fetch all events in calendar
|
|
29
|
-
* @returns the list of events
|
|
30
|
-
*/
|
|
21
|
+
fetchEvent(eventId: string): Promise<GraphEventResponse | null>;
|
|
31
22
|
fetchEvents(): Promise<{
|
|
32
23
|
value: GraphEventResponse[];
|
|
33
24
|
} | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Fetch changed events since the last delta sync.
|
|
27
|
+
* On the first call (no deltaLink), returns all current events and a deltaLink
|
|
28
|
+
* for subsequent incremental calls.
|
|
29
|
+
* @param deltaLink token from the previous call; omit for the initial full sync
|
|
30
|
+
*/
|
|
31
|
+
fetchDelta(deltaLink?: string): Promise<{
|
|
32
|
+
events: (GraphEventResponse & {
|
|
33
|
+
"@removed"?: {
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
})[];
|
|
37
|
+
deltaLink: string;
|
|
38
|
+
} | null>;
|
|
34
39
|
}
|
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CalendarManager = void 0;
|
|
4
|
-
const index_js_1 = require("../../../../config/index.js");
|
|
5
|
-
const tenantID = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_TENANT_ID");
|
|
6
|
-
const clientID = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_CLIENT_ID");
|
|
7
|
-
const clientSecret = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_CLIENT_SECRET");
|
|
8
|
-
const userMail = index_js_1.ConfigInstance.getInstance().get("MICROSOFT_USER_EMAIL");
|
|
9
4
|
/**
|
|
10
5
|
* Manager for calendar operations via Microsoft Graph API
|
|
11
6
|
*/
|
|
12
7
|
class CalendarManager {
|
|
13
|
-
|
|
8
|
+
tenantID;
|
|
9
|
+
clientID;
|
|
10
|
+
clientSecret;
|
|
11
|
+
userMail;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.tenantID = config.tenantId;
|
|
14
|
+
this.clientID = config.clientID;
|
|
15
|
+
this.clientSecret = config.clientSecret;
|
|
16
|
+
this.userMail = config.userMail;
|
|
17
|
+
}
|
|
18
|
+
get isValid() {
|
|
19
|
+
if (!this.tenantID)
|
|
20
|
+
return "Missing Microsoft Tenant ID";
|
|
21
|
+
if (!this.clientID)
|
|
22
|
+
return "Missing Microsoft Client ID";
|
|
23
|
+
if (!this.clientSecret)
|
|
24
|
+
return "Missing Microsoft Client Secret";
|
|
25
|
+
if (!this.userMail)
|
|
26
|
+
return "Missing Microsoft User Email";
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
14
29
|
async getAccess() {
|
|
15
|
-
if (
|
|
30
|
+
if (this.isValid !== true)
|
|
16
31
|
return null;
|
|
17
|
-
console.log("Getting access token...");
|
|
18
32
|
try {
|
|
19
33
|
const params = new URLSearchParams();
|
|
20
|
-
params.append("client_id", clientID);
|
|
21
|
-
params.append("client_secret", clientSecret);
|
|
34
|
+
params.append("client_id", this.clientID);
|
|
35
|
+
params.append("client_secret", this.clientSecret);
|
|
22
36
|
params.append("scope", "https://graph.microsoft.com/.default");
|
|
23
37
|
params.append("grant_type", "client_credentials");
|
|
24
|
-
const response = await fetch(`https://login.microsoftonline.com/${tenantID}/oauth2/v2.0/token`, {
|
|
38
|
+
const response = await fetch(`https://login.microsoftonline.com/${this.tenantID}/oauth2/v2.0/token`, {
|
|
25
39
|
method: "POST",
|
|
26
40
|
headers: {
|
|
27
41
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -29,26 +43,27 @@ class CalendarManager {
|
|
|
29
43
|
body: params.toString(),
|
|
30
44
|
});
|
|
31
45
|
const data = await response.json();
|
|
32
|
-
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
console.error(`[CalendarManager] Failed to obtain access token: HTTP ${response.status}`);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
33
50
|
return data;
|
|
34
51
|
}
|
|
35
52
|
catch (error) {
|
|
36
|
-
console.error("Error fetching access token:", error);
|
|
53
|
+
console.error("[CalendarManager] Error fetching access token:", error);
|
|
37
54
|
return null;
|
|
38
55
|
}
|
|
39
56
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Create a new event in the calendar
|
|
42
|
-
* @param event The event data to create
|
|
43
|
-
* @returns the created event response
|
|
44
|
-
*/
|
|
45
57
|
async createEvent(event) {
|
|
46
|
-
if (!userMail ||
|
|
58
|
+
if (!this.userMail ||
|
|
59
|
+
!this.tenantID ||
|
|
60
|
+
!this.clientID ||
|
|
61
|
+
!this.clientSecret)
|
|
47
62
|
return;
|
|
48
63
|
const accessData = await this.getAccess();
|
|
49
64
|
if (!accessData?.access_token)
|
|
50
65
|
return;
|
|
51
|
-
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events`, {
|
|
66
|
+
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events`, {
|
|
52
67
|
method: "POST",
|
|
53
68
|
headers: {
|
|
54
69
|
Authorization: `Bearer ${accessData.access_token}`,
|
|
@@ -56,21 +71,17 @@ class CalendarManager {
|
|
|
56
71
|
},
|
|
57
72
|
body: JSON.stringify(event),
|
|
58
73
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
console.error(`[CalendarManager] Failed to create event: HTTP ${res.status}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
return (await res.json());
|
|
62
79
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Update an event in the calendar
|
|
65
|
-
* @param eventId The ID of the event to update
|
|
66
|
-
* @param event The updated event data
|
|
67
|
-
* @returns
|
|
68
|
-
*/
|
|
69
80
|
async updateEvent(eventId, event) {
|
|
70
81
|
const accessData = await this.getAccess();
|
|
71
82
|
if (!accessData?.access_token)
|
|
72
83
|
return;
|
|
73
|
-
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events/${eventId}`, {
|
|
84
|
+
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, {
|
|
74
85
|
method: "PATCH",
|
|
75
86
|
headers: {
|
|
76
87
|
Authorization: `Bearer ${accessData.access_token}`,
|
|
@@ -78,46 +89,88 @@ class CalendarManager {
|
|
|
78
89
|
},
|
|
79
90
|
body: JSON.stringify(event),
|
|
80
91
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
console.error(`[CalendarManager] Failed to update event ${eventId}: HTTP ${res.status}`);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
return (await res.json());
|
|
84
97
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Delete an event from the calendar
|
|
87
|
-
* @param eventId The ID of the event to delete
|
|
88
|
-
* @returns whether the deletion was successful
|
|
89
|
-
*/
|
|
90
98
|
async deleteEvent(eventId) {
|
|
91
99
|
const accessData = await this.getAccess();
|
|
92
100
|
if (!accessData?.access_token)
|
|
93
101
|
return;
|
|
94
102
|
// on successful deletion, Microsoft Graph returns 204 No Content, so no JSON to parse
|
|
95
|
-
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events/${eventId}`, {
|
|
103
|
+
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, {
|
|
96
104
|
method: "DELETE",
|
|
97
105
|
headers: {
|
|
98
106
|
Authorization: `Bearer ${accessData.access_token}`,
|
|
99
107
|
},
|
|
100
108
|
});
|
|
101
|
-
|
|
109
|
+
if (!res.ok) {
|
|
110
|
+
console.error(`[CalendarManager] Failed to delete event ${eventId}: HTTP ${res.status}`);
|
|
111
|
+
}
|
|
102
112
|
return res.ok;
|
|
103
113
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
async fetchEvent(eventId) {
|
|
115
|
+
const accessData = await this.getAccess();
|
|
116
|
+
if (!accessData?.access_token)
|
|
117
|
+
return null;
|
|
118
|
+
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/${eventId}`, { headers: { Authorization: `Bearer ${accessData.access_token}` } });
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
console.error(`[CalendarManager] Failed to fetch event ${eventId}: HTTP ${res.status}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return (await res.json());
|
|
124
|
+
}
|
|
108
125
|
async fetchEvents() {
|
|
109
126
|
const accessData = await this.getAccess();
|
|
110
127
|
if (!accessData?.access_token)
|
|
111
128
|
return;
|
|
112
|
-
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(userMail)}/events`, {
|
|
129
|
+
const res = await fetch(`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events`, {
|
|
113
130
|
method: "GET",
|
|
114
131
|
headers: {
|
|
115
132
|
Authorization: `Bearer ${accessData.access_token}`,
|
|
116
133
|
},
|
|
117
134
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
if (!res.ok) {
|
|
136
|
+
console.error(`[CalendarManager] Failed to fetch events: HTTP ${res.status}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
return (await res.json());
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fetch changed events since the last delta sync.
|
|
143
|
+
* On the first call (no deltaLink), returns all current events and a deltaLink
|
|
144
|
+
* for subsequent incremental calls.
|
|
145
|
+
* @param deltaLink token from the previous call; omit for the initial full sync
|
|
146
|
+
*/
|
|
147
|
+
async fetchDelta(deltaLink) {
|
|
148
|
+
const accessData = await this.getAccess();
|
|
149
|
+
if (!accessData?.access_token)
|
|
150
|
+
return null;
|
|
151
|
+
const events = [];
|
|
152
|
+
let nextUrl = deltaLink ||
|
|
153
|
+
`https://graph.microsoft.com/v1.0/users/${encodeURIComponent(this.userMail)}/events/delta`;
|
|
154
|
+
let newDeltaLink = "";
|
|
155
|
+
while (nextUrl) {
|
|
156
|
+
const res = await fetch(nextUrl, {
|
|
157
|
+
headers: { Authorization: `Bearer ${accessData.access_token}` },
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
console.error(`[CalendarManager] fetchDelta HTTP ${res.status}:`, await res.text());
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const data = (await res.json());
|
|
164
|
+
events.push(...(data.value ?? []));
|
|
165
|
+
nextUrl = data["@odata.nextLink"];
|
|
166
|
+
if (data["@odata.deltaLink"])
|
|
167
|
+
newDeltaLink = data["@odata.deltaLink"];
|
|
168
|
+
}
|
|
169
|
+
if (!newDeltaLink) {
|
|
170
|
+
console.error("[CalendarManager] fetchDelta did not return a deltaLink.");
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return { events, deltaLink: newDeltaLink };
|
|
121
174
|
}
|
|
122
175
|
}
|
|
123
176
|
exports.CalendarManager = CalendarManager;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Config } from "../../../../../types/index.js";
|
|
2
|
+
export declare class CalendarSyncPoller {
|
|
3
|
+
private timer;
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config: Config);
|
|
6
|
+
private get manager();
|
|
7
|
+
/**
|
|
8
|
+
* Start polling the Microsoft Calendar delta feed.
|
|
9
|
+
* @param intervalMs polling interval in milliseconds (default: 5 minutes)
|
|
10
|
+
*/
|
|
11
|
+
start(intervalMs?: number): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
private poll;
|
|
14
|
+
private processEvent;
|
|
15
|
+
private updateScheduleFromEvent;
|
|
16
|
+
private createScheduleFromEvent;
|
|
17
|
+
private handleDeleted;
|
|
18
|
+
private loadDeltaLink;
|
|
19
|
+
private saveDeltaLink;
|
|
20
|
+
}
|