@oneuptime/common 7.0.3035 → 7.0.3040
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/Models/DatabaseModels/IncidentNoteTemplate.ts +8 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +8 -0
- package/Models/DatabaseModels/Index.ts +6 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +70 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +988 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.ts +390 -0
- package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +389 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725880508430-MigrationName.ts +269 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725881099935-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725881475134-MigrationName.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725884177663-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725898621366-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725900315712-MigrationName.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725901024444-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +18 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/IncidentStateService.ts +47 -6
- package/Server/Services/Index.ts +8 -0
- package/Server/Services/ScheduledMaintenanceService.ts +308 -1
- package/Server/Services/ScheduledMaintenanceTemplateOwnerTeamService.ts +10 -0
- package/Server/Services/ScheduledMaintenanceTemplateOwnerUserService.ts +10 -0
- package/Server/Services/ScheduledMaintenanceTemplateService.ts +318 -0
- package/Types/Database/DatabaseProperty.ts +6 -3
- package/Types/Events/Recurring.ts +86 -41
- package/Types/Permission.ts +115 -1
- package/Types/SerializableObjectDictionary.ts +2 -0
- package/UI/Components/Detail/Detail.tsx +12 -3
- package/UI/Components/Detail/Field.ts +1 -1
- package/UI/Components/Events/RecurringArrayFieldElement.tsx +104 -0
- package/UI/Components/Events/RecurringArrayViewElement.tsx +37 -0
- package/UI/Components/Events/RecurringViewElement.tsx +2 -0
- package/build/dist/Models/DatabaseModels/IncidentNoteTemplate.js +8 -0
- package/build/dist/Models/DatabaseModels/IncidentNoteTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +8 -0
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +6 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +73 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1012 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.js +402 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +401 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725880508430-MigrationName.js +98 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725880508430-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725881099935-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725881099935-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725881475134-MigrationName.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725881475134-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725884177663-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725884177663-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725898621366-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725898621366-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725900315712-MigrationName.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725900315712-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725901024444-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725901024444-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725975175669-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1725976810107-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +34 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateService.js +25 -2
- package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +7 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +209 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateOwnerUserService.js +9 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +203 -0
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +47 -20
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Permission.js +97 -1
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +2 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +9 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringArrayFieldElement.js +49 -0
- package/build/dist/UI/Components/Events/RecurringArrayFieldElement.js.map +1 -0
- package/build/dist/UI/Components/Events/RecurringArrayViewElement.js +18 -0
- package/build/dist/UI/Components/Events/RecurringArrayViewElement.js.map +1 -0
- package/build/dist/UI/Components/Events/RecurringViewElement.js +2 -1
- package/build/dist/UI/Components/Events/RecurringViewElement.js.map +1 -1
- package/package.json +2 -2
|
@@ -23,6 +23,28 @@ import ScheduledMaintenanceOwnerUser from "Common/Models/DatabaseModels/Schedule
|
|
|
23
23
|
import ScheduledMaintenanceState from "Common/Models/DatabaseModels/ScheduledMaintenanceState";
|
|
24
24
|
import ScheduledMaintenanceStateTimeline from "Common/Models/DatabaseModels/ScheduledMaintenanceStateTimeline";
|
|
25
25
|
import User from "Common/Models/DatabaseModels/User";
|
|
26
|
+
import Recurring from "../../Types/Events/Recurring";
|
|
27
|
+
import OneUptimeDate from "../../Types/Date";
|
|
28
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
29
|
+
import { FileRoute } from "Common/ServiceRoute";
|
|
30
|
+
import Dictionary from "Common/Types/Dictionary";
|
|
31
|
+
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
|
32
|
+
import SMS from "Common/Types/SMS/SMS";
|
|
33
|
+
import MailService from "Common/Server/Services/MailService";
|
|
34
|
+
import ProjectCallSMSConfigService from "Common/Server/Services/ProjectCallSMSConfigService";
|
|
35
|
+
import ProjectSmtpConfigService from "Common/Server/Services/ProjectSmtpConfigService";
|
|
36
|
+
import SmsService from "Common/Server/Services/SmsService";
|
|
37
|
+
import StatusPageResourceService from "Common/Server/Services/StatusPageResourceService";
|
|
38
|
+
import StatusPageService from "Common/Server/Services/StatusPageService";
|
|
39
|
+
import StatusPageSubscriberService from "Common/Server/Services/StatusPageSubscriberService";
|
|
40
|
+
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
|
41
|
+
import Markdown, { MarkdownContentType } from "Common/Server/Types/Markdown";
|
|
42
|
+
import logger from "Common/Server/Utils/Logger";
|
|
43
|
+
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
|
|
44
|
+
import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource";
|
|
45
|
+
import StatusPageSubscriber from "Common/Models/DatabaseModels/StatusPageSubscriber";
|
|
46
|
+
import Hostname from "../../Types/API/Hostname";
|
|
47
|
+
import Protocol from "../../Types/API/Protocol";
|
|
26
48
|
|
|
27
49
|
export class Service extends DatabaseService<Model> {
|
|
28
50
|
public constructor() {
|
|
@@ -30,6 +52,242 @@ export class Service extends DatabaseService<Model> {
|
|
|
30
52
|
this.hardDeleteItemsOlderThanInDays("createdAt", 120);
|
|
31
53
|
}
|
|
32
54
|
|
|
55
|
+
public async notififySubscribersOnEventScheduled(
|
|
56
|
+
scheduledEvents: Array<Model>,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
const host: Hostname = await DatabaseConfig.getHost();
|
|
59
|
+
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
60
|
+
|
|
61
|
+
for (const event of scheduledEvents) {
|
|
62
|
+
// get status page resources from monitors.
|
|
63
|
+
|
|
64
|
+
let statusPageResources: Array<StatusPageResource> = [];
|
|
65
|
+
|
|
66
|
+
if (event.monitors && event.monitors.length > 0) {
|
|
67
|
+
statusPageResources = await StatusPageResourceService.findBy({
|
|
68
|
+
query: {
|
|
69
|
+
monitorId: QueryHelper.any(
|
|
70
|
+
event.monitors
|
|
71
|
+
.filter((m: Monitor) => {
|
|
72
|
+
return m._id;
|
|
73
|
+
})
|
|
74
|
+
.map((m: Monitor) => {
|
|
75
|
+
return new ObjectID(m._id!);
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
props: {
|
|
80
|
+
isRoot: true,
|
|
81
|
+
ignoreHooks: true,
|
|
82
|
+
},
|
|
83
|
+
skip: 0,
|
|
84
|
+
limit: LIMIT_PER_PROJECT,
|
|
85
|
+
select: {
|
|
86
|
+
_id: true,
|
|
87
|
+
displayName: true,
|
|
88
|
+
statusPageId: true,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};
|
|
94
|
+
|
|
95
|
+
for (const resource of statusPageResources) {
|
|
96
|
+
if (!resource.statusPageId) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!statusPageToResources[resource.statusPageId?.toString()]) {
|
|
101
|
+
statusPageToResources[resource.statusPageId?.toString()] = [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
statusPageToResources[resource.statusPageId?.toString()]?.push(
|
|
105
|
+
resource,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const statusPages: Array<StatusPage> =
|
|
110
|
+
await StatusPageSubscriberService.getStatusPagesToSendNotification(
|
|
111
|
+
event.statusPages?.map((i: StatusPage) => {
|
|
112
|
+
return i.id!;
|
|
113
|
+
}) || [],
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
for (const statuspage of statusPages) {
|
|
117
|
+
if (!statuspage.id) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const subscribers: Array<StatusPageSubscriber> =
|
|
122
|
+
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
|
123
|
+
statuspage.id!,
|
|
124
|
+
{
|
|
125
|
+
isRoot: true,
|
|
126
|
+
ignoreHooks: true,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
131
|
+
statuspage.id,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const statusPageName: string =
|
|
135
|
+
statuspage.pageTitle || statuspage.name || "Status Page";
|
|
136
|
+
|
|
137
|
+
// Send email to Email subscribers.
|
|
138
|
+
|
|
139
|
+
const resourcesAffected: string =
|
|
140
|
+
statusPageToResources[statuspage._id!]
|
|
141
|
+
?.map((r: StatusPageResource) => {
|
|
142
|
+
return r.displayName;
|
|
143
|
+
})
|
|
144
|
+
.join(", ") || "";
|
|
145
|
+
|
|
146
|
+
for (const subscriber of subscribers) {
|
|
147
|
+
if (!subscriber._id) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const shouldNotifySubscriber: boolean =
|
|
152
|
+
StatusPageSubscriberService.shouldSendNotification({
|
|
153
|
+
subscriber: subscriber,
|
|
154
|
+
statusPageResources: statusPageToResources[statuspage._id!] || [],
|
|
155
|
+
statusPage: statuspage,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (!shouldNotifySubscriber) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const unsubscribeUrl: string =
|
|
163
|
+
StatusPageSubscriberService.getUnsubscribeLink(
|
|
164
|
+
URL.fromString(statusPageURL),
|
|
165
|
+
subscriber.id!,
|
|
166
|
+
).toString();
|
|
167
|
+
|
|
168
|
+
if (subscriber.subscriberPhone) {
|
|
169
|
+
const sms: SMS = {
|
|
170
|
+
message: `
|
|
171
|
+
Scheduled Maintenance - ${statusPageName}
|
|
172
|
+
|
|
173
|
+
${event.title || ""}
|
|
174
|
+
|
|
175
|
+
${
|
|
176
|
+
resourcesAffected
|
|
177
|
+
? "Resources Affected: " + resourcesAffected
|
|
178
|
+
: ""
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
To view this event, visit ${statusPageURL}
|
|
182
|
+
|
|
183
|
+
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
|
|
184
|
+
`,
|
|
185
|
+
to: subscriber.subscriberPhone,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// send sms here.
|
|
189
|
+
SmsService.sendSms(sms, {
|
|
190
|
+
projectId: statuspage.projectId,
|
|
191
|
+
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
|
|
192
|
+
statuspage.callSmsConfig,
|
|
193
|
+
),
|
|
194
|
+
}).catch((err: Error) => {
|
|
195
|
+
logger.error(err);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (subscriber.subscriberEmail) {
|
|
200
|
+
// send email here.
|
|
201
|
+
|
|
202
|
+
MailService.sendMail(
|
|
203
|
+
{
|
|
204
|
+
toEmail: subscriber.subscriberEmail,
|
|
205
|
+
templateType:
|
|
206
|
+
EmailTemplateType.SubscriberScheduledMaintenanceEventCreated,
|
|
207
|
+
vars: {
|
|
208
|
+
statusPageName: statusPageName,
|
|
209
|
+
statusPageUrl: statusPageURL,
|
|
210
|
+
logoUrl: statuspage.logoFileId
|
|
211
|
+
? new URL(httpProtocol, host)
|
|
212
|
+
.addRoute(FileRoute)
|
|
213
|
+
.addRoute("/image/" + statuspage.logoFileId)
|
|
214
|
+
.toString()
|
|
215
|
+
: "",
|
|
216
|
+
isPublicStatusPage: statuspage.isPublicStatusPage
|
|
217
|
+
? "true"
|
|
218
|
+
: "false",
|
|
219
|
+
resourcesAffected: resourcesAffected,
|
|
220
|
+
scheduledAt:
|
|
221
|
+
OneUptimeDate.getDateAsFormattedHTMLInMultipleTimezones({
|
|
222
|
+
date: event.startsAt!,
|
|
223
|
+
timezones: statuspage.subscriberTimezones || [],
|
|
224
|
+
}),
|
|
225
|
+
eventTitle: event.title || "",
|
|
226
|
+
eventDescription: await Markdown.convertToHTML(
|
|
227
|
+
event.description || "",
|
|
228
|
+
MarkdownContentType.Email,
|
|
229
|
+
),
|
|
230
|
+
unsubscribeUrl: unsubscribeUrl,
|
|
231
|
+
},
|
|
232
|
+
subject: "[Scheduled Maintenance] " + statusPageName,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
mailServer: ProjectSmtpConfigService.toEmailServer(
|
|
236
|
+
statuspage.smtpConfig,
|
|
237
|
+
),
|
|
238
|
+
projectId: statuspage.projectId!,
|
|
239
|
+
},
|
|
240
|
+
).catch((err: Error) => {
|
|
241
|
+
logger.error(err);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
protected override async onBeforeUpdate(
|
|
250
|
+
updateBy: UpdateBy<Model>,
|
|
251
|
+
): Promise<OnUpdate<Model>> {
|
|
252
|
+
if (
|
|
253
|
+
updateBy.query.id &&
|
|
254
|
+
updateBy.data.sendSubscriberNotificationsOnBeforeTheEvent
|
|
255
|
+
) {
|
|
256
|
+
const scheduledMaintenance: Model | null = await this.findOneById({
|
|
257
|
+
id: updateBy.query.id! as ObjectID,
|
|
258
|
+
select: {
|
|
259
|
+
startsAt: true,
|
|
260
|
+
},
|
|
261
|
+
props: {
|
|
262
|
+
isRoot: true,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (!scheduledMaintenance) {
|
|
267
|
+
throw new BadDataException("Scheduled Maintennace Event not found");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const startsAt: Date =
|
|
271
|
+
(updateBy.data.startsAt as Date) ||
|
|
272
|
+
(scheduledMaintenance.startsAt! as Date);
|
|
273
|
+
|
|
274
|
+
const nextTimeToNotifyBeforeTheEvent: Date | null =
|
|
275
|
+
this.getNextTimeToNotify({
|
|
276
|
+
eventScheduledDate: startsAt,
|
|
277
|
+
sendSubscriberNotifiationsOn: updateBy.data
|
|
278
|
+
.sendSubscriberNotificationsOnBeforeTheEvent as Array<Recurring>,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
updateBy.data.nextSubscriberNotificationBeforeTheEventAt =
|
|
282
|
+
nextTimeToNotifyBeforeTheEvent;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
updateBy,
|
|
287
|
+
carryForward: null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
33
291
|
protected override async onBeforeDelete(
|
|
34
292
|
deleteBy: DeleteBy<Model>,
|
|
35
293
|
): Promise<OnDelete<Model>> {
|
|
@@ -73,10 +331,40 @@ export class Service extends DatabaseService<Model> {
|
|
|
73
331
|
return onDelete;
|
|
74
332
|
}
|
|
75
333
|
|
|
334
|
+
public getNextTimeToNotify(data: {
|
|
335
|
+
eventScheduledDate: Date;
|
|
336
|
+
sendSubscriberNotifiationsOn: Array<Recurring>;
|
|
337
|
+
}): Date | null {
|
|
338
|
+
let recurringDate: Date | null = null;
|
|
339
|
+
|
|
340
|
+
for (const recurringItem of data.sendSubscriberNotifiationsOn) {
|
|
341
|
+
const notificationDate: Date = Recurring.getNextDateInterval(
|
|
342
|
+
data.eventScheduledDate,
|
|
343
|
+
recurringItem,
|
|
344
|
+
true,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
// if this date is in the future. set it to recurring date.
|
|
348
|
+
if (OneUptimeDate.isInTheFuture(notificationDate)) {
|
|
349
|
+
recurringDate = notificationDate;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// if this new date is less than the recurring date then set it to recuring date. We need to get the least date.
|
|
353
|
+
|
|
354
|
+
if (recurringDate) {
|
|
355
|
+
if (OneUptimeDate.isBefore(notificationDate, recurringDate)) {
|
|
356
|
+
recurringDate = notificationDate;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return recurringDate;
|
|
362
|
+
}
|
|
363
|
+
|
|
76
364
|
protected override async onBeforeCreate(
|
|
77
365
|
createBy: CreateBy<Model>,
|
|
78
366
|
): Promise<OnCreate<Model>> {
|
|
79
|
-
if (!createBy.props.tenantId) {
|
|
367
|
+
if (!createBy.props.tenantId && !createBy.data.projectId) {
|
|
80
368
|
throw new BadDataException(
|
|
81
369
|
"ProjectId required to create scheduled maintenane.",
|
|
82
370
|
);
|
|
@@ -105,6 +393,25 @@ export class Service extends DatabaseService<Model> {
|
|
|
105
393
|
createBy.data.currentScheduledMaintenanceStateId =
|
|
106
394
|
scheduledMaintenanceState.id;
|
|
107
395
|
|
|
396
|
+
// get next notification date.
|
|
397
|
+
|
|
398
|
+
if (
|
|
399
|
+
createBy.data.sendSubscriberNotificationsOnBeforeTheEvent &&
|
|
400
|
+
createBy.data.startsAt
|
|
401
|
+
) {
|
|
402
|
+
const nextNotificationDate: Date | null = this.getNextTimeToNotify({
|
|
403
|
+
eventScheduledDate: createBy.data.startsAt,
|
|
404
|
+
sendSubscriberNotifiationsOn:
|
|
405
|
+
createBy.data.sendSubscriberNotificationsOnBeforeTheEvent,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (nextNotificationDate) {
|
|
409
|
+
// set this.
|
|
410
|
+
createBy.data.nextSubscriberNotificationBeforeTheEventAt =
|
|
411
|
+
nextNotificationDate;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
108
415
|
return { createBy, carryForward: null };
|
|
109
416
|
}
|
|
110
417
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default new Service();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import DatabaseService from "./DatabaseService";
|
|
2
|
+
import Model from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser";
|
|
3
|
+
|
|
4
|
+
export class Service extends DatabaseService<Model> {
|
|
5
|
+
public constructor() {
|
|
6
|
+
super(Model);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default new Service();
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
|
|
2
|
+
import DatabaseService from "./DatabaseService";
|
|
3
|
+
import ScheduledMaintenanceTemplateOwnerTeamService from "./ScheduledMaintenanceTemplateOwnerTeamService";
|
|
4
|
+
import ScheduledMaintenanceTemplateOwnerUserService from "./ScheduledMaintenanceTemplateOwnerUserService";
|
|
5
|
+
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
6
|
+
import ObjectID from "../../Types/ObjectID";
|
|
7
|
+
import Typeof from "../../Types/Typeof";
|
|
8
|
+
import Model from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplate";
|
|
9
|
+
import ScheduledMaintenanceTemplateOwnerTeam from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerTeam";
|
|
10
|
+
import ScheduledMaintenanceTemplateOwnerUser from "Common/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser";
|
|
11
|
+
import CreateBy from "../Types/Database/CreateBy";
|
|
12
|
+
import OneUptimeDate from "../../Types/Date";
|
|
13
|
+
import Recurring from "../../Types/Events/Recurring";
|
|
14
|
+
import UpdateBy from "../Types/Database/UpdateBy";
|
|
15
|
+
import QueryDeepPartialEntity from "../../Types/Database/PartialEntity";
|
|
16
|
+
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
17
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
18
|
+
|
|
19
|
+
export class Service extends DatabaseService<Model> {
|
|
20
|
+
public constructor() {
|
|
21
|
+
super(Model);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public validateEventTemplate(template: Model): void {
|
|
25
|
+
// if recurring then start, end, scheduled time should not be null and all of them should be in the future.
|
|
26
|
+
if (template.isRecurringEvent) {
|
|
27
|
+
const startDate: Date | undefined = template.firstEventStartsAt;
|
|
28
|
+
const endDate: Date | undefined = template.firstEventEndsAt;
|
|
29
|
+
const scheduledTime: Date | undefined = template.firstEventScheduledAt;
|
|
30
|
+
|
|
31
|
+
if (!startDate) {
|
|
32
|
+
throw new BadDataException(
|
|
33
|
+
"Start date is required for recurring events.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!endDate) {
|
|
38
|
+
throw new BadDataException(
|
|
39
|
+
"End date is required for recurring events.",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!scheduledTime) {
|
|
44
|
+
throw new BadDataException(
|
|
45
|
+
"Scheduled time is required for recurring events.",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// check if all dates are in the future.
|
|
50
|
+
|
|
51
|
+
if (OneUptimeDate.isInTheFuture(startDate) === false) {
|
|
52
|
+
throw new BadDataException("Start date should be in the future.");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (OneUptimeDate.isInTheFuture(endDate) === false) {
|
|
56
|
+
throw new BadDataException("End date should be in the future.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (OneUptimeDate.isInTheFuture(scheduledTime) === false) {
|
|
60
|
+
throw new BadDataException("Scheduled time should be in the future.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// make sure scheduedDate is < start date
|
|
64
|
+
if (!OneUptimeDate.isBefore(scheduledTime, startDate)) {
|
|
65
|
+
throw new BadDataException(
|
|
66
|
+
"Scheduled time should be less than start date.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// make sure scheduledDate is < end date
|
|
71
|
+
|
|
72
|
+
if (!OneUptimeDate.isBefore(scheduledTime, endDate)) {
|
|
73
|
+
throw new BadDataException(
|
|
74
|
+
"Scheduled time should be less than end date.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// make sure start date is < end date
|
|
79
|
+
|
|
80
|
+
if (!OneUptimeDate.isBefore(startDate, endDate)) {
|
|
81
|
+
throw new BadDataException("Start date should be less than end date.");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// check recurring internval
|
|
85
|
+
|
|
86
|
+
if (template.recurringInterval === undefined) {
|
|
87
|
+
throw new BadDataException(
|
|
88
|
+
"Recurring interval is required for recurring events.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public getNextEventTime(data: {
|
|
95
|
+
dateAndTime: Date;
|
|
96
|
+
recurringInterval: Recurring;
|
|
97
|
+
}): Date {
|
|
98
|
+
// check if firstScheduledAt is in the future, and if yes return that.
|
|
99
|
+
|
|
100
|
+
if (OneUptimeDate.isInTheFuture(data.dateAndTime)) {
|
|
101
|
+
return data.dateAndTime;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// if not then calculate the next event time.
|
|
105
|
+
|
|
106
|
+
return Recurring.getNextDate(data.dateAndTime, data.recurringInterval);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected override async onBeforeCreate(
|
|
110
|
+
createBy: CreateBy<Model>,
|
|
111
|
+
): Promise<OnCreate<Model>> {
|
|
112
|
+
this.validateEventTemplate(createBy.data);
|
|
113
|
+
|
|
114
|
+
if (createBy.data.isRecurringEvent) {
|
|
115
|
+
// if all is good then the next scheduled at time should be set.
|
|
116
|
+
createBy.data.scheduleNextEventAt = this.getNextEventTime({
|
|
117
|
+
dateAndTime: createBy.data.firstEventScheduledAt!,
|
|
118
|
+
recurringInterval: createBy.data.recurringInterval!,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
createBy: createBy,
|
|
124
|
+
carryForward: false,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected override async onBeforeUpdate(
|
|
129
|
+
updateBy: UpdateBy<Model>,
|
|
130
|
+
): Promise<OnUpdate<Model>> {
|
|
131
|
+
const newTemplate: QueryDeepPartialEntity<Model> = updateBy.data;
|
|
132
|
+
|
|
133
|
+
const existingTemplates: Array<Model> = await this.findBy({
|
|
134
|
+
query: updateBy.query,
|
|
135
|
+
select: {
|
|
136
|
+
_id: true,
|
|
137
|
+
isRecurringEvent: true,
|
|
138
|
+
firstEventScheduledAt: true,
|
|
139
|
+
recurringInterval: true,
|
|
140
|
+
firstEventEndsAt: true,
|
|
141
|
+
firstEventStartsAt: true,
|
|
142
|
+
},
|
|
143
|
+
limit: LIMIT_MAX,
|
|
144
|
+
skip: 0,
|
|
145
|
+
props: {
|
|
146
|
+
isRoot: true,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
for (const template of existingTemplates) {
|
|
151
|
+
let isRecurring: boolean = Boolean(template.isRecurringEvent);
|
|
152
|
+
|
|
153
|
+
if (Object.keys(newTemplate).includes("isRecurringEvent")) {
|
|
154
|
+
isRecurring = newTemplate.isRecurringEvent as boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let firstEventScheduledAt: Date | undefined =
|
|
158
|
+
template.firstEventScheduledAt;
|
|
159
|
+
|
|
160
|
+
if (Object.keys(newTemplate).includes("firstEventScheduledAt")) {
|
|
161
|
+
firstEventScheduledAt = newTemplate.firstEventScheduledAt as Date;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let recurringInterval: Recurring | undefined = template.recurringInterval;
|
|
165
|
+
|
|
166
|
+
if (Object.keys(newTemplate).includes("recurringInterval")) {
|
|
167
|
+
recurringInterval = newTemplate.recurringInterval as Recurring;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let firstEventEndsAt: Date | undefined = template.firstEventEndsAt;
|
|
171
|
+
|
|
172
|
+
if (Object.keys(newTemplate).includes("firstEventEndsAt")) {
|
|
173
|
+
firstEventEndsAt = newTemplate.firstEventEndsAt as Date;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let firstEventStartsAt: Date | undefined = template.firstEventStartsAt;
|
|
177
|
+
|
|
178
|
+
if (Object.keys(newTemplate).includes("firstEventStartsAt")) {
|
|
179
|
+
firstEventStartsAt = newTemplate.firstEventStartsAt as Date;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isRecurring) {
|
|
183
|
+
// make sure all are not null.
|
|
184
|
+
|
|
185
|
+
if (!firstEventScheduledAt) {
|
|
186
|
+
throw new BadDataException(
|
|
187
|
+
"First event scheduled at is required for recurring events.",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!recurringInterval) {
|
|
192
|
+
throw new BadDataException(
|
|
193
|
+
"Recurring interval is required for recurring events.",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!firstEventEndsAt) {
|
|
198
|
+
throw new BadDataException(
|
|
199
|
+
"First event ends at is required for recurring events.",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!firstEventStartsAt) {
|
|
204
|
+
throw new BadDataException(
|
|
205
|
+
"First event starts at is required for recurring events.",
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// make sure scheduedDate is < start date
|
|
210
|
+
if (
|
|
211
|
+
!OneUptimeDate.isBefore(firstEventScheduledAt, firstEventStartsAt)
|
|
212
|
+
) {
|
|
213
|
+
throw new BadDataException(
|
|
214
|
+
"Scheduled time should be less than start date.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// make sure scheduledDate is < end date
|
|
219
|
+
|
|
220
|
+
if (!OneUptimeDate.isBefore(firstEventScheduledAt, firstEventEndsAt)) {
|
|
221
|
+
throw new BadDataException(
|
|
222
|
+
"Scheduled time should be less than end date.",
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// make sure start date is < end date
|
|
227
|
+
|
|
228
|
+
if (!OneUptimeDate.isBefore(firstEventStartsAt, firstEventEndsAt)) {
|
|
229
|
+
throw new BadDataException(
|
|
230
|
+
"Start date should be less than end date.",
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// now get next interval time.
|
|
235
|
+
|
|
236
|
+
newTemplate.scheduleNextEventAt = this.getNextEventTime({
|
|
237
|
+
dateAndTime: firstEventScheduledAt,
|
|
238
|
+
recurringInterval: recurringInterval,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
updateBy.data = newTemplate;
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
updateBy: updateBy,
|
|
247
|
+
carryForward: false,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
protected override async onCreateSuccess(
|
|
252
|
+
onCreate: OnCreate<Model>,
|
|
253
|
+
createdItem: Model,
|
|
254
|
+
): Promise<Model> {
|
|
255
|
+
// add owners.
|
|
256
|
+
|
|
257
|
+
if (
|
|
258
|
+
createdItem.projectId &&
|
|
259
|
+
createdItem.id &&
|
|
260
|
+
onCreate.createBy.miscDataProps &&
|
|
261
|
+
(onCreate.createBy.miscDataProps["ownerTeams"] ||
|
|
262
|
+
onCreate.createBy.miscDataProps["ownerUsers"])
|
|
263
|
+
) {
|
|
264
|
+
await this.addOwners(
|
|
265
|
+
createdItem.projectId,
|
|
266
|
+
createdItem.id,
|
|
267
|
+
(onCreate.createBy.miscDataProps["ownerUsers"] as Array<ObjectID>) ||
|
|
268
|
+
[],
|
|
269
|
+
(onCreate.createBy.miscDataProps["ownerTeams"] as Array<ObjectID>) ||
|
|
270
|
+
[],
|
|
271
|
+
onCreate.createBy.props,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return createdItem;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
public async addOwners(
|
|
279
|
+
projectId: ObjectID,
|
|
280
|
+
scheduledMaintenanceTemplateId: ObjectID,
|
|
281
|
+
userIds: Array<ObjectID>,
|
|
282
|
+
teamIds: Array<ObjectID>,
|
|
283
|
+
props: DatabaseCommonInteractionProps,
|
|
284
|
+
): Promise<void> {
|
|
285
|
+
for (let teamId of teamIds) {
|
|
286
|
+
if (typeof teamId === Typeof.String) {
|
|
287
|
+
teamId = new ObjectID(teamId.toString());
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const teamOwner: ScheduledMaintenanceTemplateOwnerTeam =
|
|
291
|
+
new ScheduledMaintenanceTemplateOwnerTeam();
|
|
292
|
+
teamOwner.scheduledMaintenanceTemplateId = scheduledMaintenanceTemplateId;
|
|
293
|
+
teamOwner.projectId = projectId;
|
|
294
|
+
teamOwner.teamId = teamId;
|
|
295
|
+
|
|
296
|
+
await ScheduledMaintenanceTemplateOwnerTeamService.create({
|
|
297
|
+
data: teamOwner,
|
|
298
|
+
props: props,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (let userId of userIds) {
|
|
303
|
+
if (typeof userId === Typeof.String) {
|
|
304
|
+
userId = new ObjectID(userId.toString());
|
|
305
|
+
}
|
|
306
|
+
const teamOwner: ScheduledMaintenanceTemplateOwnerUser =
|
|
307
|
+
new ScheduledMaintenanceTemplateOwnerUser();
|
|
308
|
+
teamOwner.scheduledMaintenanceTemplateId = scheduledMaintenanceTemplateId;
|
|
309
|
+
teamOwner.projectId = projectId;
|
|
310
|
+
teamOwner.userId = userId;
|
|
311
|
+
await ScheduledMaintenanceTemplateOwnerUserService.create({
|
|
312
|
+
data: teamOwner,
|
|
313
|
+
props: props,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
export default new Service();
|
|
@@ -11,19 +11,22 @@ export default class DatabaseProperty extends SerializableObject {
|
|
|
11
11
|
|
|
12
12
|
protected static fromDatabase(
|
|
13
13
|
_value: string | number | JSONObject | JSONArray,
|
|
14
|
-
): DatabaseProperty | null {
|
|
14
|
+
): DatabaseProperty | Array<DatabaseProperty> | null {
|
|
15
15
|
throw new NotImplementedException();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
protected static toDatabase(
|
|
19
|
-
_value:
|
|
19
|
+
_value:
|
|
20
|
+
| DatabaseProperty
|
|
21
|
+
| Array<DatabaseProperty>
|
|
22
|
+
| FindOperator<DatabaseProperty>,
|
|
20
23
|
): string | number | JSONObject | JSONArray | null {
|
|
21
24
|
throw new NotImplementedException();
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
protected static _fromDatabase(
|
|
25
28
|
value: string | number | JSONObject | JSONArray,
|
|
26
|
-
): DatabaseProperty | null {
|
|
29
|
+
): DatabaseProperty | Array<DatabaseProperty> | null {
|
|
27
30
|
return this.fromDatabase(value);
|
|
28
31
|
}
|
|
29
32
|
|