@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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CalendarSyncPoller = void 0;
|
|
7
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
8
|
+
const eventToSchedule_js_1 = require("../functions/eventToSchedule.js");
|
|
9
|
+
const calendarMetaHelper_js_1 = require("./calendarMetaHelper.js");
|
|
10
|
+
const CalendarManager_js_1 = require("./CalendarManager.js");
|
|
11
|
+
const index_js_1 = require("../../../../../types/index.js");
|
|
12
|
+
class CalendarSyncPoller {
|
|
13
|
+
timer = null;
|
|
14
|
+
config;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
get manager() {
|
|
19
|
+
const configValues = JSON.parse(this.config.get("value"));
|
|
20
|
+
return new CalendarManager_js_1.CalendarManager({
|
|
21
|
+
tenantId: configValues.tenant_id,
|
|
22
|
+
clientID: configValues.client_id,
|
|
23
|
+
clientSecret: configValues.client_secret,
|
|
24
|
+
userMail: configValues.user_email,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start polling the Microsoft Calendar delta feed.
|
|
29
|
+
* @param intervalMs polling interval in milliseconds (default: 5 minutes)
|
|
30
|
+
*/
|
|
31
|
+
start(intervalMs = 5 * 60 * 1000) {
|
|
32
|
+
if (this.manager?.isValid !== true) {
|
|
33
|
+
console.error(`[CalendarSyncPoller] Not started — invalid configuration: ${this.manager?.isValid}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.poll().catch(console.error);
|
|
37
|
+
this.timer = setInterval(() => this.poll().catch(console.error), intervalMs);
|
|
38
|
+
}
|
|
39
|
+
stop() {
|
|
40
|
+
if (this.timer)
|
|
41
|
+
clearInterval(this.timer);
|
|
42
|
+
}
|
|
43
|
+
async poll() {
|
|
44
|
+
const deltaLink = await this.loadDeltaLink();
|
|
45
|
+
const result = await this.manager?.fetchDelta(deltaLink);
|
|
46
|
+
if (!result) {
|
|
47
|
+
console.error("[CalendarSyncPoller] Failed to fetch calendar delta.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
for (const event of result.events) {
|
|
51
|
+
try {
|
|
52
|
+
await this.processEvent(event);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
console.error("[CalendarSyncPoller] Error processing event:", event.id, err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await this.saveDeltaLink(result.deltaLink);
|
|
59
|
+
}
|
|
60
|
+
async processEvent(event) {
|
|
61
|
+
if (event["@removed"]) {
|
|
62
|
+
await this.handleDeleted(event.id);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Exception events are individual moved/edited occurrences of a series.
|
|
66
|
+
// The series master handles the recurrence as a whole; exceptions are noise.
|
|
67
|
+
if (event.type === "exception")
|
|
68
|
+
return;
|
|
69
|
+
const metaEntry = await (0, calendarMetaHelper_js_1.findCalendarMetaByEventId)(event.id);
|
|
70
|
+
if (metaEntry) {
|
|
71
|
+
const storedChangeKey = metaEntry.get("values")?.microsoftCalendarChangeKey;
|
|
72
|
+
// Skip if we caused this change (our write already stored this changeKey)
|
|
73
|
+
if (event.changeKey && storedChangeKey === event.changeKey)
|
|
74
|
+
return;
|
|
75
|
+
await this.updateScheduleFromEvent(metaEntry, event);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Delta responses omit most properties; fetch the full event before creating a schedule
|
|
79
|
+
const fullEvent = await this.manager?.fetchEvent(event.id);
|
|
80
|
+
if (!fullEvent)
|
|
81
|
+
return;
|
|
82
|
+
await this.createScheduleFromEvent(fullEvent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async updateScheduleFromEvent(metaEntry, event) {
|
|
86
|
+
const scheduleId = metaEntry.get("entryObjectId");
|
|
87
|
+
if (!scheduleId)
|
|
88
|
+
return;
|
|
89
|
+
// Delta responses omit most fields; fetch the full event for title/body/cron
|
|
90
|
+
const fullEvent = (await this.manager?.fetchEvent(event.id)) ?? event;
|
|
91
|
+
const schedule = await new node_1.default.Query(index_js_1.Maintenance_Schedule).get(scheduleId, { useMasterKey: true });
|
|
92
|
+
if (fullEvent.subject)
|
|
93
|
+
schedule.set("title", fullEvent.subject);
|
|
94
|
+
if (fullEvent.bodyPreview)
|
|
95
|
+
schedule.set("description", fullEvent.bodyPreview);
|
|
96
|
+
const cronData = (0, eventToSchedule_js_1.eventToSchedule)(fullEvent);
|
|
97
|
+
if (cronData) {
|
|
98
|
+
const { notifyBeforeDue, ...cron } = cronData;
|
|
99
|
+
schedule.set("cron", cron);
|
|
100
|
+
if (notifyBeforeDue != null)
|
|
101
|
+
schedule.set("notifyBeforeDue", notifyBeforeDue);
|
|
102
|
+
}
|
|
103
|
+
// useMasterKey prevents afterSaveHook from re-syncing back to calendar
|
|
104
|
+
await schedule.save(null, { useMasterKey: true });
|
|
105
|
+
if (event.changeKey) {
|
|
106
|
+
await (0, calendarMetaHelper_js_1.updateCalendarChangeKey)(metaEntry, event.changeKey, fullEvent);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async createScheduleFromEvent(event) {
|
|
110
|
+
const parsed = parseEventSubject(event.subject);
|
|
111
|
+
let source;
|
|
112
|
+
const tenant = this.config.get("tenant");
|
|
113
|
+
if (parsed) {
|
|
114
|
+
source = await new node_1.default.Query(index_js_1.Source)
|
|
115
|
+
.equalTo("name", parsed.sourceName)
|
|
116
|
+
.equalTo("tenant", tenant)
|
|
117
|
+
.first({ useMasterKey: true });
|
|
118
|
+
}
|
|
119
|
+
if (!source) {
|
|
120
|
+
const configValues = JSON.parse(this.config.get("value"));
|
|
121
|
+
if (!configValues.default_source_id) {
|
|
122
|
+
console.error(`[CalendarSyncPoller] Skipping event ${event.id} — no source name in subject and no default_source_id configured.`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
source = await new node_1.default.Query(index_js_1.Source)
|
|
126
|
+
.equalTo("objectId", configValues.default_source_id)
|
|
127
|
+
.first({ useMasterKey: true });
|
|
128
|
+
if (!source) {
|
|
129
|
+
console.error(`[CalendarSyncPoller] Skipping event ${event.id} — default source "${configValues.default_source_id}" not found.`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const schedule = new index_js_1.Maintenance_Schedule();
|
|
134
|
+
schedule.set("title", event.subject);
|
|
135
|
+
schedule.set("source", source);
|
|
136
|
+
schedule.set("enabled", true);
|
|
137
|
+
schedule.set("tenant", tenant);
|
|
138
|
+
if (event.bodyPreview)
|
|
139
|
+
schedule.set("description", event.bodyPreview);
|
|
140
|
+
const cronData = (0, eventToSchedule_js_1.eventToSchedule)(event);
|
|
141
|
+
if (cronData) {
|
|
142
|
+
const { notifyBeforeDue, ...cron } = cronData;
|
|
143
|
+
schedule.set("cron", cron);
|
|
144
|
+
if (notifyBeforeDue != null)
|
|
145
|
+
schedule.set("notifyBeforeDue", notifyBeforeDue);
|
|
146
|
+
}
|
|
147
|
+
// useMasterKey prevents afterSaveHook from syncing back to calendar
|
|
148
|
+
await schedule.save(null, { useMasterKey: true });
|
|
149
|
+
await (0, calendarMetaHelper_js_1.saveCalendarMeta)(schedule.id, event.id, event.changeKey, tenant, event);
|
|
150
|
+
}
|
|
151
|
+
async handleDeleted(eventId) {
|
|
152
|
+
const metaEntry = await (0, calendarMetaHelper_js_1.findCalendarMetaByEventId)(eventId);
|
|
153
|
+
if (!metaEntry)
|
|
154
|
+
return;
|
|
155
|
+
const scheduleId = metaEntry.get("entryObjectId");
|
|
156
|
+
if (scheduleId) {
|
|
157
|
+
try {
|
|
158
|
+
const schedule = await new node_1.default.Query(index_js_1.Maintenance_Schedule).get(scheduleId, { useMasterKey: true });
|
|
159
|
+
// Disable rather than delete to preserve execution history
|
|
160
|
+
schedule.set("enabled", false);
|
|
161
|
+
await schedule.save(null, { useMasterKey: true });
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
console.error("[CalendarSyncPoller] Could not disable schedule for deleted event:", scheduleId, err);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await metaEntry.destroy({ useMasterKey: true });
|
|
168
|
+
}
|
|
169
|
+
async loadDeltaLink() {
|
|
170
|
+
this.config = await this.config.fetch({ useMasterKey: true });
|
|
171
|
+
const values = JSON.parse(this.config.get("value"));
|
|
172
|
+
return values.delta_link;
|
|
173
|
+
}
|
|
174
|
+
async saveDeltaLink(link) {
|
|
175
|
+
this.config = await this.config.fetch({ useMasterKey: true });
|
|
176
|
+
const values = JSON.parse(this.config.get("value"));
|
|
177
|
+
values.delta_link = link;
|
|
178
|
+
this.config.set("value", JSON.stringify(values));
|
|
179
|
+
await this.config.save(null, { useMasterKey: true });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.CalendarSyncPoller = CalendarSyncPoller;
|
|
183
|
+
// Parses the source name out of an Outlook-originated event subject.
|
|
184
|
+
// Expected format for externally-created events: "<source.name>" - <anything>
|
|
185
|
+
// Quotes around the source name are optional.
|
|
186
|
+
function parseEventSubject(subject) {
|
|
187
|
+
if (!subject)
|
|
188
|
+
return null;
|
|
189
|
+
const separatorIdx = subject.indexOf(" - ");
|
|
190
|
+
if (separatorIdx === -1)
|
|
191
|
+
return null;
|
|
192
|
+
const rawSource = subject.slice(0, separatorIdx).trim();
|
|
193
|
+
if (!rawSource)
|
|
194
|
+
return null;
|
|
195
|
+
const sourceName = rawSource.replace(/^"(.*)"$/, "$1");
|
|
196
|
+
return { sourceName };
|
|
197
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Meta_Entry, Tenant } from "../../../../../types/index.js";
|
|
2
|
+
import { MetaConfig } from "../../../../../types/custom/MetaField.js";
|
|
3
|
+
import { GraphEventResponse } from "../types/Event.js";
|
|
4
|
+
export declare const CALENDAR_SYNC_CONTEXT: string;
|
|
5
|
+
export interface CalendarEventSnapshot {
|
|
6
|
+
id: string;
|
|
7
|
+
subject?: string;
|
|
8
|
+
start: {
|
|
9
|
+
dateTime: string;
|
|
10
|
+
timeZone: string;
|
|
11
|
+
};
|
|
12
|
+
end: {
|
|
13
|
+
dateTime: string;
|
|
14
|
+
timeZone: string;
|
|
15
|
+
};
|
|
16
|
+
isAllDay?: boolean;
|
|
17
|
+
isCancelled?: boolean;
|
|
18
|
+
body?: {
|
|
19
|
+
contentType?: string;
|
|
20
|
+
content?: string;
|
|
21
|
+
};
|
|
22
|
+
recurrence?: GraphEventResponse["recurrence"];
|
|
23
|
+
location?: {
|
|
24
|
+
displayName?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface CalendarMetaValues {
|
|
28
|
+
microsoftCalendarEventId: string;
|
|
29
|
+
microsoftCalendarChangeKey?: string;
|
|
30
|
+
microsoftCalendarEvent?: CalendarEventSnapshot;
|
|
31
|
+
}
|
|
32
|
+
export declare const CalendarMetaConfig: MetaConfig;
|
|
33
|
+
export declare function findCalendarMetaForSchedule(scheduleId: string): Promise<Meta_Entry | null>;
|
|
34
|
+
export declare function findCalendarMetaByEventId(eventId: string): Promise<Meta_Entry | null>;
|
|
35
|
+
export declare function saveCalendarMeta(scheduleId: string, eventId: string, changeKey?: string, tenant?: Tenant, event?: GraphEventResponse): Promise<Meta_Entry>;
|
|
36
|
+
export declare function updateCalendarChangeKey(entry: Meta_Entry, changeKey: string, event?: GraphEventResponse): Promise<void>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CalendarMetaConfig = exports.CALENDAR_SYNC_CONTEXT = void 0;
|
|
7
|
+
exports.findCalendarMetaForSchedule = findCalendarMetaForSchedule;
|
|
8
|
+
exports.findCalendarMetaByEventId = findCalendarMetaByEventId;
|
|
9
|
+
exports.saveCalendarMeta = saveCalendarMeta;
|
|
10
|
+
exports.updateCalendarChangeKey = updateCalendarChangeKey;
|
|
11
|
+
const node_1 = __importDefault(require("parse/node"));
|
|
12
|
+
const index_js_1 = require("../../../../../types/index.js");
|
|
13
|
+
exports.CALENDAR_SYNC_CONTEXT = JSON.stringify([
|
|
14
|
+
"schedule",
|
|
15
|
+
"sync",
|
|
16
|
+
"microsoft",
|
|
17
|
+
]);
|
|
18
|
+
function toEventSnapshot(event) {
|
|
19
|
+
return {
|
|
20
|
+
id: event.id,
|
|
21
|
+
subject: event.subject,
|
|
22
|
+
start: event.start,
|
|
23
|
+
end: event.end,
|
|
24
|
+
isAllDay: event.isAllDay,
|
|
25
|
+
isCancelled: event.isCancelled,
|
|
26
|
+
body: event.body
|
|
27
|
+
? { contentType: event.body.contentType, content: event.body.content }
|
|
28
|
+
: undefined,
|
|
29
|
+
recurrence: event.recurrence ?? undefined,
|
|
30
|
+
location: event.location?.displayName
|
|
31
|
+
? { displayName: event.location.displayName }
|
|
32
|
+
: undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
exports.CalendarMetaConfig = {
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
name: "microsoftCalendarEventId",
|
|
39
|
+
type: "string",
|
|
40
|
+
label: "Microsoft Calendar Event ID",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "microsoftCalendarChangeKey",
|
|
44
|
+
type: "string",
|
|
45
|
+
label: "Microsoft Calendar Change Key",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
async function findCalendarMetaForSchedule(scheduleId) {
|
|
50
|
+
return ((await new node_1.default.Query(index_js_1.Meta_Entry)
|
|
51
|
+
.equalTo("context", exports.CALENDAR_SYNC_CONTEXT)
|
|
52
|
+
.equalTo("entryClassName", index_js_1.Maintenance_Schedule.className)
|
|
53
|
+
.equalTo("entryObjectId", scheduleId)
|
|
54
|
+
.first({ useMasterKey: true })) ?? null);
|
|
55
|
+
}
|
|
56
|
+
async function findCalendarMetaByEventId(eventId) {
|
|
57
|
+
return ((await new node_1.default.Query(index_js_1.Meta_Entry)
|
|
58
|
+
.equalTo("context", exports.CALENDAR_SYNC_CONTEXT)
|
|
59
|
+
.equalTo("entryClassName", index_js_1.Maintenance_Schedule.className)
|
|
60
|
+
// @ts-expect-error
|
|
61
|
+
.equalTo("values.microsoftCalendarEventId", eventId)
|
|
62
|
+
.first({ useMasterKey: true })) ?? null);
|
|
63
|
+
}
|
|
64
|
+
async function saveCalendarMeta(scheduleId, eventId, changeKey, tenant, event) {
|
|
65
|
+
let entry = await findCalendarMetaForSchedule(scheduleId);
|
|
66
|
+
if (!entry) {
|
|
67
|
+
entry = new index_js_1.Meta_Entry();
|
|
68
|
+
entry.set("context", exports.CALENDAR_SYNC_CONTEXT);
|
|
69
|
+
entry.set("entryClassName", index_js_1.Maintenance_Schedule.className);
|
|
70
|
+
entry.set("entryObjectId", scheduleId);
|
|
71
|
+
entry.set("tenant", tenant);
|
|
72
|
+
entry.set("config", {});
|
|
73
|
+
}
|
|
74
|
+
const values = { microsoftCalendarEventId: eventId };
|
|
75
|
+
if (changeKey)
|
|
76
|
+
values.microsoftCalendarChangeKey = changeKey;
|
|
77
|
+
if (event)
|
|
78
|
+
values.microsoftCalendarEvent = toEventSnapshot(event);
|
|
79
|
+
entry.set("values", values);
|
|
80
|
+
return entry.save(null, { useMasterKey: true });
|
|
81
|
+
}
|
|
82
|
+
async function updateCalendarChangeKey(entry, changeKey, event) {
|
|
83
|
+
entry.set("values", {
|
|
84
|
+
...entry.get("values"),
|
|
85
|
+
microsoftCalendarChangeKey: changeKey,
|
|
86
|
+
...(event ? { microsoftCalendarEvent: toEventSnapshot(event) } : {}),
|
|
87
|
+
});
|
|
88
|
+
await entry.save(null, { useMasterKey: true });
|
|
89
|
+
}
|
|
@@ -6,8 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.initSchedulesFeature = initSchedulesFeature;
|
|
7
7
|
const node_1 = __importDefault(require("parse/node"));
|
|
8
8
|
const index_js_1 = require("../../../types/index.js");
|
|
9
|
+
const CalendarSyncPoller_js_1 = require("./calendarSync/service/CalendarSyncPoller.js");
|
|
9
10
|
async function initSchedulesFeature() {
|
|
10
11
|
updateFiinishedSchedules();
|
|
12
|
+
const configs = await new node_1.default.Query(index_js_1.Config)
|
|
13
|
+
.equalTo("key", "OPENSERVICE_MICROSOFT_OUTLOOK_INTEGRATION")
|
|
14
|
+
.includeAll()
|
|
15
|
+
.findAll({ useMasterKey: true });
|
|
16
|
+
const pollers = configs.map((config) => new CalendarSyncPoller_js_1.CalendarSyncPoller(config).start(1000 * 60));
|
|
11
17
|
}
|
|
12
18
|
async function updateFiinishedSchedules() {
|
|
13
19
|
const executionsToUpdate = await new node_1.default.Query(index_js_1.Maintenance_Schedule_Execution)
|
|
@@ -45,6 +45,8 @@ async function init() {
|
|
|
45
45
|
//object.value can look like this: [{"label":"PSA","allowedValues":[],"required":false},{"label":"Typ","allowedValues":[],"required":false,"onlyOneAllowedValue":false},{"label":"Material","allowedValues":["@OD3_Maintenance_Item"],"required":false,"onlyOneAllowedValue":false},{"label":"Dienstleister","allowedValues":["@OD3_COMPANY","@OD3_CONTACT"],"required":false,"onlyOneAllowedValue":false}]
|
|
46
46
|
//fields in Maintenance_Step can look like this: [{"value":"Handschuhe","metafield":{"label":"PSA","allowedValues":[],"required":false}},{"value":"Auffüllen","metafield":{"label":"Typ","allowedValues":[],"required":false,"onlyOneAllowedValue":false}}]
|
|
47
47
|
const metafieldsconfig = JSON.parse(object.get("value"));
|
|
48
|
+
if (!Array.isArray(metafieldsconfig))
|
|
49
|
+
return;
|
|
48
50
|
const [maintenanceStepQueryError, maintenanceStepQuery] = await (0, catchError_js_1.catchError)(new node_1.default.Query(index_js_2.Maintenance_Schedule_Step).find({
|
|
49
51
|
useMasterKey: true,
|
|
50
52
|
}));
|
|
@@ -6,12 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.init = init;
|
|
7
7
|
const node_1 = __importDefault(require("parse/node"));
|
|
8
8
|
const scheduleToEvent_1 = require("../features/openservice/schedules/calendarSync/functions/scheduleToEvent");
|
|
9
|
+
const calendarMetaHelper_1 = require("../features/openservice/schedules/calendarSync/service/calendarMetaHelper");
|
|
9
10
|
const CalendarManager_1 = require("../features/openservice/schedules/calendarSync/service/CalendarManager");
|
|
10
11
|
const schema_1 = require("../features/schema");
|
|
11
12
|
const types_1 = require("../types");
|
|
12
13
|
async function init() {
|
|
13
14
|
(0, schema_1.beforeSaveHook)(types_1.Maintenance_Schedule, async (request) => {
|
|
14
|
-
const { object, original, user } = request;
|
|
15
15
|
await (0, schema_1.defaultHandler)(request);
|
|
16
16
|
// await defaultAclHandler(request, { allowCustomACL: true });
|
|
17
17
|
});
|
|
@@ -51,8 +51,8 @@ async function init() {
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
(0, schema_1.beforeDeleteHook)(types_1.Maintenance_Schedule, async (request) => {
|
|
54
|
-
const { object
|
|
55
|
-
const scheduleFetched = await object?.fetchWithInclude(["template", "source"], {
|
|
54
|
+
const { object } = request;
|
|
55
|
+
const scheduleFetched = await object?.fetchWithInclude(["template", "source", "tenant"], {
|
|
56
56
|
useMasterKey: true,
|
|
57
57
|
});
|
|
58
58
|
const template = scheduleFetched?.get("template");
|
|
@@ -60,9 +60,16 @@ async function init() {
|
|
|
60
60
|
if (source)
|
|
61
61
|
template?.relation("sources").remove(source);
|
|
62
62
|
await template?.save(null, { useMasterKey: true });
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
63
|
+
const metaEntry = await (0, calendarMetaHelper_1.findCalendarMetaForSchedule)(object.id);
|
|
64
|
+
if (metaEntry) {
|
|
65
|
+
const eventId = metaEntry.get("values")?.microsoftCalendarEventId;
|
|
66
|
+
if (eventId) {
|
|
67
|
+
const calendarManager = await getScheduleCalendarManager(object);
|
|
68
|
+
if (!calendarManager)
|
|
69
|
+
return;
|
|
70
|
+
await calendarManager.deleteEvent(eventId);
|
|
71
|
+
}
|
|
72
|
+
await metaEntry.destroy({ useMasterKey: true });
|
|
66
73
|
}
|
|
67
74
|
});
|
|
68
75
|
}
|
|
@@ -85,15 +92,20 @@ async function addToTemplateSources(schedule) {
|
|
|
85
92
|
await template.save(null, { useMasterKey: true });
|
|
86
93
|
}
|
|
87
94
|
async function updateCalendarEvent(schedule) {
|
|
88
|
-
const
|
|
95
|
+
const metaEntry = await (0, calendarMetaHelper_1.findCalendarMetaForSchedule)(schedule.id);
|
|
96
|
+
const snapshot = metaEntry?.get("values")?.microsoftCalendarEvent;
|
|
97
|
+
const event = await (0, scheduleToEvent_1.scheduleToEvent)(schedule, snapshot);
|
|
89
98
|
if (!event)
|
|
90
99
|
return;
|
|
91
|
-
const calendarManager =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await calendarManager.updateEvent(
|
|
100
|
+
const calendarManager = await getScheduleCalendarManager(schedule);
|
|
101
|
+
if (!calendarManager)
|
|
102
|
+
return;
|
|
103
|
+
const eventId = metaEntry?.get("values")?.microsoftCalendarEventId;
|
|
104
|
+
if (eventId) {
|
|
105
|
+
const updatedEvent = await calendarManager.updateEvent(eventId, event);
|
|
106
|
+
if (updatedEvent?.changeKey && metaEntry) {
|
|
107
|
+
await (0, calendarMetaHelper_1.updateCalendarChangeKey)(metaEntry, updatedEvent.changeKey, updatedEvent);
|
|
108
|
+
}
|
|
97
109
|
}
|
|
98
110
|
else {
|
|
99
111
|
await addToCalendar(schedule);
|
|
@@ -104,18 +116,31 @@ async function updateCalendarEvent(schedule) {
|
|
|
104
116
|
* @param schedule
|
|
105
117
|
*/
|
|
106
118
|
async function addToCalendar(schedule) {
|
|
107
|
-
console.log("Creating new calendar event for schedule...");
|
|
108
119
|
const event = await (0, scheduleToEvent_1.scheduleToEvent)(schedule);
|
|
109
120
|
if (!event)
|
|
110
121
|
return;
|
|
111
|
-
const calendarManager =
|
|
122
|
+
const calendarManager = await getScheduleCalendarManager(schedule);
|
|
123
|
+
if (!calendarManager)
|
|
124
|
+
return;
|
|
112
125
|
const createdEvent = await calendarManager.createEvent(event);
|
|
113
|
-
if (createdEvent
|
|
114
|
-
|
|
115
|
-
schedule.set("meta", {
|
|
116
|
-
...existingMeta,
|
|
117
|
-
microsoftCalendarEventId: createdEvent.id,
|
|
118
|
-
});
|
|
119
|
-
await schedule.save(null, { useMasterKey: true });
|
|
126
|
+
if (createdEvent?.id) {
|
|
127
|
+
await (0, calendarMetaHelper_1.saveCalendarMeta)(schedule.id, createdEvent.id, createdEvent.changeKey, schedule.get("tenant"), createdEvent);
|
|
120
128
|
}
|
|
121
129
|
}
|
|
130
|
+
async function getScheduleCalendarManager(schedule) {
|
|
131
|
+
const config = await new node_1.default.Query(types_1.Config)
|
|
132
|
+
.equalTo("key", "OPENSERVICE_MICROSOFT_OUTLOOK_INTEGRATION")
|
|
133
|
+
.equalTo("tenant", schedule.get("tenant"))
|
|
134
|
+
.first({ useMasterKey: true });
|
|
135
|
+
if (!config) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const configValues = JSON.parse(config.get("value"));
|
|
139
|
+
const calendarManager = new CalendarManager_1.CalendarManager({
|
|
140
|
+
tenantId: configValues.tenant_id,
|
|
141
|
+
clientID: configValues.client_id,
|
|
142
|
+
clientSecret: configValues.client_secret,
|
|
143
|
+
userMail: configValues.user_email,
|
|
144
|
+
});
|
|
145
|
+
return calendarManager;
|
|
146
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import Parse from "parse/node";
|
|
2
2
|
import type { Tenant } from "./Tenant";
|
|
3
|
+
import { MetaConfig } from "./custom/MetaField";
|
|
3
4
|
export interface Meta_ConfigAttributes {
|
|
4
5
|
id: string;
|
|
5
6
|
objectId: string;
|
|
6
7
|
createdAt: Date;
|
|
7
8
|
updatedAt: Date;
|
|
8
|
-
config:
|
|
9
|
+
config: MetaConfig;
|
|
9
10
|
context?: string | undefined;
|
|
10
11
|
tenant?: Tenant | undefined;
|
|
11
12
|
}
|
|
12
13
|
export declare class Meta_Config extends Parse.Object<Meta_ConfigAttributes> {
|
|
13
14
|
static className: string;
|
|
14
15
|
constructor(data?: Partial<Meta_ConfigAttributes>);
|
|
15
|
-
get config():
|
|
16
|
-
set config(value:
|
|
16
|
+
get config(): MetaConfig;
|
|
17
|
+
set config(value: MetaConfig);
|
|
17
18
|
get context(): string | undefined;
|
|
18
19
|
set context(value: string | undefined);
|
|
19
20
|
get tenant(): Tenant | undefined;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import Parse from "parse/node";
|
|
2
2
|
import type { Meta_Config } from "./Meta_Config";
|
|
3
3
|
import type { Tenant } from "./Tenant";
|
|
4
|
+
import { MetaConfig } from "./custom/MetaField";
|
|
4
5
|
export interface Meta_EntryAttributes {
|
|
5
6
|
id: string;
|
|
6
7
|
objectId: string;
|
|
7
8
|
createdAt: Date;
|
|
8
9
|
updatedAt: Date;
|
|
9
|
-
config?:
|
|
10
|
+
config?: MetaConfig | undefined;
|
|
10
11
|
context?: string | undefined;
|
|
11
12
|
entryClassName?: string | undefined;
|
|
12
13
|
entryObjectId?: string | undefined;
|
|
@@ -17,8 +18,8 @@ export interface Meta_EntryAttributes {
|
|
|
17
18
|
export declare class Meta_Entry extends Parse.Object<Meta_EntryAttributes> {
|
|
18
19
|
static className: string;
|
|
19
20
|
constructor(data?: Partial<Meta_EntryAttributes>);
|
|
20
|
-
get config():
|
|
21
|
-
set config(value:
|
|
21
|
+
get config(): MetaConfig | undefined;
|
|
22
|
+
set config(value: MetaConfig | undefined);
|
|
22
23
|
get context(): string | undefined;
|
|
23
24
|
set context(value: string | undefined);
|
|
24
25
|
get entryClassName(): string | undefined;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base properties shared by all meta field types
|
|
3
|
+
*/
|
|
4
|
+
export type MetaFieldBaseConfig = {
|
|
5
|
+
name: string;
|
|
6
|
+
label: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
help?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
/** Default value for the field */
|
|
11
|
+
defaultValue?: any;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* String field type with optional validation
|
|
15
|
+
*/
|
|
16
|
+
export type MetaFieldString = MetaFieldBaseConfig & {
|
|
17
|
+
type: "string";
|
|
18
|
+
minLength?: number;
|
|
19
|
+
maxLength?: number;
|
|
20
|
+
pattern?: string;
|
|
21
|
+
multiline?: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Number field type with min/max constraints
|
|
25
|
+
*/
|
|
26
|
+
export type MetaFieldNumber = MetaFieldBaseConfig & {
|
|
27
|
+
type: "number";
|
|
28
|
+
min?: number;
|
|
29
|
+
max?: number;
|
|
30
|
+
step?: number;
|
|
31
|
+
unit?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Boolean field type (checkbox/switch)
|
|
35
|
+
*/
|
|
36
|
+
export type MetaFieldBoolean = MetaFieldBaseConfig & {
|
|
37
|
+
type: "boolean";
|
|
38
|
+
defaultValue?: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Date field type with optional date range constraints
|
|
42
|
+
*/
|
|
43
|
+
export type MetaFieldDate = MetaFieldBaseConfig & {
|
|
44
|
+
type: "date";
|
|
45
|
+
minDate?: Date;
|
|
46
|
+
maxDate?: Date;
|
|
47
|
+
includeTime?: boolean;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Select field type with predefined options
|
|
51
|
+
*/
|
|
52
|
+
export type MetaFieldSelect = MetaFieldBaseConfig & {
|
|
53
|
+
type: "select";
|
|
54
|
+
options: Array<{
|
|
55
|
+
label: string;
|
|
56
|
+
value: string;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
}>;
|
|
59
|
+
/** Optional grouping for select options */
|
|
60
|
+
groups?: Array<{
|
|
61
|
+
label: string;
|
|
62
|
+
options: Array<{
|
|
63
|
+
label: string;
|
|
64
|
+
value: string;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
}>;
|
|
67
|
+
}>;
|
|
68
|
+
multiple?: boolean;
|
|
69
|
+
defaultValue?: string | string[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Parse select field type for selecting Parse objects
|
|
73
|
+
*/
|
|
74
|
+
export type MetaFieldSelectParse = MetaFieldBaseConfig & {
|
|
75
|
+
type: "select_parse";
|
|
76
|
+
className: string;
|
|
77
|
+
display?: string;
|
|
78
|
+
multiple?: boolean;
|
|
79
|
+
defaultValue?: string | string[];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Array of all available field types
|
|
83
|
+
*/
|
|
84
|
+
export declare const META_FIELD_TYPES: readonly ["string", "number", "boolean", "date", "select", "select_parse"];
|
|
85
|
+
/**
|
|
86
|
+
* Literal type for field types derived from the array
|
|
87
|
+
*/
|
|
88
|
+
export type MetaFieldTypes = (typeof META_FIELD_TYPES)[number];
|
|
89
|
+
/**
|
|
90
|
+
* Mapped type for accessing MetaField types by their string literal
|
|
91
|
+
* @example MetaFieldMap["string"] => MetaFieldString
|
|
92
|
+
*/
|
|
93
|
+
export type MetaFieldMap = {
|
|
94
|
+
string: MetaFieldString;
|
|
95
|
+
number: MetaFieldNumber;
|
|
96
|
+
boolean: MetaFieldBoolean;
|
|
97
|
+
date: MetaFieldDate;
|
|
98
|
+
select: MetaFieldSelect;
|
|
99
|
+
select_parse: MetaFieldSelectParse;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Union type of all specific meta field types
|
|
103
|
+
*/
|
|
104
|
+
export type MetaFieldConfig = MetaFieldString | MetaFieldNumber | MetaFieldBoolean | MetaFieldDate | MetaFieldSelect | MetaFieldSelectParse;
|
|
105
|
+
/**
|
|
106
|
+
* Helper type to extract specific field type from MetaField union
|
|
107
|
+
*/
|
|
108
|
+
export type MetaFieldOfType<T extends MetaFieldTypes> = Extract<MetaFieldConfig, {
|
|
109
|
+
type: T;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Type guard to check if a field is of a specific type
|
|
113
|
+
*/
|
|
114
|
+
export declare function isMetaFieldType<T extends MetaFieldTypes>(field: MetaFieldConfig, type: T): field is MetaFieldOfType<T>;
|
|
115
|
+
export type MetaConfig = {
|
|
116
|
+
fields: MetaFieldConfig[];
|
|
117
|
+
};
|