oneuptime/App/FeatureSet/Workers/Jobs/ScheduledMaintenanceStateTimeline/SendNotificationToSubscribers.ts

295 lines
10 KiB
TypeScript
Raw Normal View History

import RunCron from "../../Utils/Cron";
import { FileRoute } from "Common/ServiceRoute";
import Hostname from "Common/Types/API/Hostname";
import Protocol from "Common/Types/API/Protocol";
import URL from "Common/Types/API/URL";
import LIMIT_MAX, { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import OneUptimeDate from "Common/Types/Date";
import Dictionary from "Common/Types/Dictionary";
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
import ObjectID from "Common/Types/ObjectID";
import SMS from "Common/Types/SMS/SMS";
import Text from "Common/Types/Text";
import { EVERY_MINUTE } from "Common/Utils/CronTime";
import DatabaseConfig from "CommonServer/DatabaseConfig";
import MailService from "CommonServer/Services/MailService";
import ProjectCallSMSConfigService from "CommonServer/Services/ProjectCallSMSConfigService";
import ProjectSmtpConfigService from "CommonServer/Services/ProjectSmtpConfigService";
import ScheduledMaintenanceService from "CommonServer/Services/ScheduledMaintenanceService";
import ScheduledMaintenanceStateTimelineService from "CommonServer/Services/ScheduledMaintenanceStateTimelineService";
import SmsService from "CommonServer/Services/SmsService";
import StatusPageResourceService from "CommonServer/Services/StatusPageResourceService";
import StatusPageService from "CommonServer/Services/StatusPageService";
import StatusPageSubscriberService from "CommonServer/Services/StatusPageSubscriberService";
import QueryHelper from "CommonServer/Types/Database/QueryHelper";
import logger from "CommonServer/Utils/Logger";
import Monitor from "Common/AppModels/Models/Monitor";
import ScheduledMaintenance from "Common/AppModels/Models/ScheduledMaintenance";
import ScheduledMaintenanceStateTimeline from "Common/AppModels/Models/ScheduledMaintenanceStateTimeline";
import StatusPage from "Common/AppModels/Models/StatusPage";
import StatusPageResource from "Common/AppModels/Models/StatusPageResource";
import StatusPageSubscriber from "Common/AppModels/Models/StatusPageSubscriber";
2024-01-11 10:49:55 +00:00
RunCron(
"ScheduledMaintenanceStateTimeline:SendNotificationToSubscribers",
{ schedule: EVERY_MINUTE, runOnStartup: false },
async () => {
const host: Hostname = await DatabaseConfig.getHost();
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
2024-01-11 10:49:55 +00:00
const scheduledEventStateTimelines: Array<ScheduledMaintenanceStateTimeline> =
await ScheduledMaintenanceStateTimelineService.findBy({
query: {
isStatusPageSubscribersNotified: false,
shouldStatusPageSubscribersBeNotified: true,
createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()),
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
select: {
_id: true,
scheduledMaintenanceId: true,
scheduledMaintenanceStateId: true,
scheduledMaintenanceState: {
name: true,
},
},
});
2024-01-11 10:49:55 +00:00
for (const scheduledEventStateTimeline of scheduledEventStateTimelines) {
await ScheduledMaintenanceStateTimelineService.updateOneById({
id: scheduledEventStateTimeline.id!,
data: {
isStatusPageSubscribersNotified: true,
},
props: {
isRoot: true,
ignoreHooks: true,
},
});
2024-01-11 10:49:55 +00:00
if (
!scheduledEventStateTimeline.scheduledMaintenanceId ||
!scheduledEventStateTimeline.scheduledMaintenanceStateId
) {
continue;
}
2024-01-11 10:49:55 +00:00
if (!scheduledEventStateTimeline.scheduledMaintenanceState?.name) {
continue;
}
2024-01-11 10:49:55 +00:00
// get all scheduled events of all the projects.
const event: ScheduledMaintenance | null =
await ScheduledMaintenanceService.findOneById({
id: scheduledEventStateTimeline.scheduledMaintenanceId!,
props: {
isRoot: true,
},
2024-01-11 10:49:55 +00:00
select: {
_id: true,
title: true,
description: true,
startsAt: true,
monitors: {
_id: true,
},
statusPages: {
_id: true,
},
},
});
2024-01-11 10:49:55 +00:00
if (!event) {
continue;
}
2024-01-11 10:49:55 +00:00
// get status page resources from monitors.
2024-01-11 10:49:55 +00:00
let statusPageResources: Array<StatusPageResource> = [];
if (event.monitors && event.monitors.length > 0) {
statusPageResources = await StatusPageResourceService.findBy({
query: {
monitorId: QueryHelper.any(
event.monitors
.filter((m: Monitor) => {
return m._id;
})
.map((m: Monitor) => {
return new ObjectID(m._id!);
}),
),
},
props: {
isRoot: true,
ignoreHooks: true,
},
skip: 0,
limit: LIMIT_PER_PROJECT,
select: {
_id: true,
displayName: true,
statusPageId: true,
},
});
}
2024-01-11 10:49:55 +00:00
const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};
2024-01-11 10:49:55 +00:00
for (const resource of statusPageResources) {
if (!resource.statusPageId) {
continue;
}
2024-01-11 10:49:55 +00:00
if (!statusPageToResources[resource.statusPageId?.toString()]) {
statusPageToResources[resource.statusPageId?.toString()] = [];
}
2024-01-11 10:49:55 +00:00
statusPageToResources[resource.statusPageId?.toString()]?.push(
resource,
);
}
2024-01-11 10:49:55 +00:00
const statusPages: Array<StatusPage> =
await StatusPageSubscriberService.getStatusPagesToSendNotification(
event.statusPages?.map((i: StatusPage) => {
return i.id!;
}) || [],
);
2024-01-11 10:49:55 +00:00
for (const statuspage of statusPages) {
if (!statuspage.id) {
continue;
}
2024-01-11 10:49:55 +00:00
const subscribers: Array<StatusPageSubscriber> =
await StatusPageSubscriberService.getSubscribersByStatusPage(
statuspage.id!,
{
isRoot: true,
ignoreHooks: true,
},
);
2024-01-11 10:49:55 +00:00
const statusPageURL: string = await StatusPageService.getStatusPageURL(
statuspage.id,
);
2024-01-11 10:49:55 +00:00
const statusPageName: string =
statuspage.pageTitle || statuspage.name || "Status Page";
2024-01-11 10:49:55 +00:00
// Send email to Email subscribers.
2024-01-11 10:49:55 +00:00
for (const subscriber of subscribers) {
if (!subscriber._id) {
continue;
}
2024-01-11 10:49:55 +00:00
const shouldNotifySubscriber: boolean =
StatusPageSubscriberService.shouldSendNotification({
subscriber: subscriber,
statusPageResources: statusPageToResources[statuspage._id!] || [],
statusPage: statuspage,
});
2024-01-11 10:49:55 +00:00
if (!shouldNotifySubscriber) {
continue;
}
2024-01-11 10:49:55 +00:00
const unsubscribeUrl: string =
StatusPageSubscriberService.getUnsubscribeLink(
URL.fromString(statusPageURL),
subscriber.id!,
).toString();
2024-01-11 10:49:55 +00:00
if (subscriber.subscriberPhone) {
const sms: SMS = {
message: `
2024-01-11 10:49:55 +00:00
${statusPageName} - Scheduled maintenance event - ${
event.title || ""
} - state changed to ${
scheduledEventStateTimeline
.scheduledMaintenanceState?.name
}
2024-01-11 10:49:55 +00:00
To view this note, visit ${statusPageURL}
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
`,
to: subscriber.subscriberPhone,
};
2024-01-11 10:49:55 +00:00
// send sms here.
SmsService.sendSms(sms, {
projectId: statuspage.projectId,
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
statuspage.callSmsConfig,
),
}).catch((err: Error) => {
logger.error(err);
});
}
2024-01-11 10:49:55 +00:00
if (subscriber.subscriberEmail) {
// send email here.
2024-01-11 10:49:55 +00:00
MailService.sendMail(
{
toEmail: subscriber.subscriberEmail,
templateType:
EmailTemplateType.SubscriberScheduledMaintenanceEventStateChanged,
vars: {
statusPageName: statusPageName,
statusPageUrl: statusPageURL,
logoUrl: statuspage.logoFileId
? new URL(httpProtocol, host)
.addRoute(FileRoute)
.addRoute("/image/" + statuspage.logoFileId)
.toString()
: "",
isPublicStatusPage: statuspage.isPublicStatusPage
? "true"
: "false",
resourcesAffected:
statusPageToResources[statuspage._id!]
?.map((r: StatusPageResource) => {
return r.displayName;
})
.join(", ") || "",
2024-01-11 10:49:55 +00:00
eventState:
scheduledEventStateTimeline.scheduledMaintenanceState
?.name || "",
2024-01-11 10:49:55 +00:00
scheduledAt: OneUptimeDate.getDateAsFormattedString(
event.startsAt!,
),
eventTitle: event.title || "",
eventDescription: event.description || "",
unsubscribeUrl: unsubscribeUrl,
},
subject: `[Scheduled Maintenance ${Text.uppercaseFirstLetter(
scheduledEventStateTimeline.scheduledMaintenanceState?.name,
)}] ${statusPageName}`,
},
{
mailServer: ProjectSmtpConfigService.toEmailServer(
statuspage.smtpConfig,
),
projectId: statuspage.projectId,
},
).catch((err: Error) => {
logger.error(err);
});
}
2024-01-11 10:49:55 +00:00
}
}
2024-01-11 10:49:55 +00:00
}
},
2024-01-11 10:49:55 +00:00
);