mirror of
https://github.com/OneUptime/oneuptime
synced 2024-11-21 22:59:07 +00:00
286 lines
9.6 KiB
TypeScript
286 lines
9.6 KiB
TypeScript
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 "Common/Server/DatabaseConfig";
|
|
import IncidentService from "Common/Server/Services/IncidentService";
|
|
import IncidentStateTimelineService from "Common/Server/Services/IncidentStateTimelineService";
|
|
import MailService from "Common/Server/Services/MailService";
|
|
import ProjectCallSMSConfigService from "Common/Server/Services/ProjectCallSMSConfigService";
|
|
import ProjectSMTPConfigService from "Common/Server/Services/ProjectSmtpConfigService";
|
|
import SmsService from "Common/Server/Services/SmsService";
|
|
import StatusPageResourceService from "Common/Server/Services/StatusPageResourceService";
|
|
import StatusPageService from "Common/Server/Services/StatusPageService";
|
|
import StatusPageSubscriberService from "Common/Server/Services/StatusPageSubscriberService";
|
|
import QueryHelper from "Common/Server/Types/Database/QueryHelper";
|
|
import logger from "Common/Server/Utils/Logger";
|
|
import Incident from "Common/Models/DatabaseModels/Incident";
|
|
import IncidentStateTimeline from "Common/Models/DatabaseModels/IncidentStateTimeline";
|
|
import Monitor from "Common/Models/DatabaseModels/Monitor";
|
|
import StatusPage from "Common/Models/DatabaseModels/StatusPage";
|
|
import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource";
|
|
import StatusPageSubscriber from "Common/Models/DatabaseModels/StatusPageSubscriber";
|
|
import StatusPageEventType from "Common/Types/StatusPage/StatusPageEventType";
|
|
|
|
RunCron(
|
|
"IncidentStateTimeline:SendNotificationToSubscribers",
|
|
{ schedule: EVERY_MINUTE, runOnStartup: false },
|
|
async () => {
|
|
const incidentStateTimelines: Array<IncidentStateTimeline> =
|
|
await IncidentStateTimelineService.findBy({
|
|
query: {
|
|
isStatusPageSubscribersNotified: false,
|
|
shouldStatusPageSubscribersBeNotified: true,
|
|
createdAt: QueryHelper.lessThan(OneUptimeDate.getCurrentDate()),
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_MAX,
|
|
skip: 0,
|
|
select: {
|
|
_id: true,
|
|
incidentId: true,
|
|
incidentStateId: true,
|
|
incidentState: {
|
|
name: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
for (const incidentStateTimeline of incidentStateTimelines) {
|
|
await IncidentStateTimelineService.updateOneById({
|
|
id: incidentStateTimeline.id!,
|
|
data: {
|
|
isStatusPageSubscribersNotified: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
ignoreHooks: true,
|
|
},
|
|
});
|
|
|
|
if (
|
|
!incidentStateTimeline.incidentId ||
|
|
!incidentStateTimeline.incidentStateId
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (!incidentStateTimeline.incidentState?.name) {
|
|
continue;
|
|
}
|
|
|
|
// get all scheduled events of all the projects.
|
|
const incident: Incident | null = await IncidentService.findOneById({
|
|
id: incidentStateTimeline.incidentId!,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
description: true,
|
|
monitors: {
|
|
_id: true,
|
|
},
|
|
incidentSeverity: {
|
|
name: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!incident) {
|
|
continue;
|
|
}
|
|
|
|
if (!incident.monitors || incident.monitors.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
// get status page resources from monitors.
|
|
|
|
const statusPageResources: Array<StatusPageResource> =
|
|
await StatusPageResourceService.findBy({
|
|
query: {
|
|
monitorId: QueryHelper.any(
|
|
incident.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,
|
|
},
|
|
});
|
|
|
|
const statusPageToResources: Dictionary<Array<StatusPageResource>> = {};
|
|
|
|
for (const resource of statusPageResources) {
|
|
if (!resource.statusPageId) {
|
|
continue;
|
|
}
|
|
|
|
if (!statusPageToResources[resource.statusPageId?.toString()]) {
|
|
statusPageToResources[resource.statusPageId?.toString()] = [];
|
|
}
|
|
|
|
statusPageToResources[resource.statusPageId?.toString()]?.push(
|
|
resource,
|
|
);
|
|
}
|
|
|
|
const statusPages: Array<StatusPage> =
|
|
await StatusPageSubscriberService.getStatusPagesToSendNotification(
|
|
Object.keys(statusPageToResources).map((i: string) => {
|
|
return new ObjectID(i);
|
|
}),
|
|
);
|
|
|
|
for (const statuspage of statusPages) {
|
|
if (!statuspage.id) {
|
|
continue;
|
|
}
|
|
|
|
const subscribers: Array<StatusPageSubscriber> =
|
|
await StatusPageSubscriberService.getSubscribersByStatusPage(
|
|
statuspage.id!,
|
|
{
|
|
isRoot: true,
|
|
ignoreHooks: true,
|
|
},
|
|
);
|
|
|
|
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
statuspage.id,
|
|
);
|
|
const statusPageName: string =
|
|
statuspage.pageTitle || statuspage.name || "Status Page";
|
|
|
|
// Send email to Email subscribers.
|
|
|
|
for (const subscriber of subscribers) {
|
|
if (!subscriber._id) {
|
|
continue;
|
|
}
|
|
|
|
const shouldNotifySubscriber: boolean =
|
|
StatusPageSubscriberService.shouldSendNotification({
|
|
subscriber: subscriber,
|
|
statusPageResources: statusPageToResources[statuspage._id!] || [],
|
|
statusPage: statuspage,
|
|
eventType: StatusPageEventType.Incident,
|
|
});
|
|
|
|
if (!shouldNotifySubscriber) {
|
|
continue;
|
|
}
|
|
|
|
const unsubscribeUrl: string =
|
|
StatusPageSubscriberService.getUnsubscribeLink(
|
|
URL.fromString(statusPageURL),
|
|
subscriber.id!,
|
|
).toString();
|
|
|
|
if (subscriber.subscriberPhone) {
|
|
const sms: SMS = {
|
|
message: `
|
|
Incident ${Text.uppercaseFirstLetter(
|
|
incidentStateTimeline.incidentState.name,
|
|
)} - ${statusPageName}
|
|
|
|
To view this incident, visit ${statusPageURL}
|
|
|
|
To update notification preferences or unsubscribe, visit ${unsubscribeUrl}
|
|
`,
|
|
to: subscriber.subscriberPhone,
|
|
};
|
|
|
|
// send sms here.
|
|
SmsService.sendSms(sms, {
|
|
projectId: statuspage.projectId,
|
|
customTwilioConfig: ProjectCallSMSConfigService.toTwilioConfig(
|
|
statuspage.callSmsConfig,
|
|
),
|
|
}).catch((err: Error) => {
|
|
logger.error(err);
|
|
});
|
|
}
|
|
|
|
if (subscriber.subscriberEmail) {
|
|
// send email here.
|
|
|
|
MailService.sendMail(
|
|
{
|
|
toEmail: subscriber.subscriberEmail,
|
|
templateType: EmailTemplateType.SubscriberIncidentStateChanged,
|
|
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(", ") || "None",
|
|
incidentSeverity: incident.incidentSeverity?.name || " - ",
|
|
incidentTitle: incident.title || "",
|
|
incidentDescription: incident.description || "",
|
|
|
|
incidentState: incidentStateTimeline.incidentState.name,
|
|
unsubscribeUrl: unsubscribeUrl,
|
|
},
|
|
subject: `[Incident ${Text.uppercaseFirstLetter(
|
|
incidentStateTimeline.incidentState.name,
|
|
)}] ${statusPageName}`,
|
|
},
|
|
{
|
|
mailServer: ProjectSMTPConfigService.toEmailServer(
|
|
statuspage.smtpConfig,
|
|
),
|
|
projectId: statuspage.projectId,
|
|
},
|
|
).catch((err: Error) => {
|
|
logger.error(err);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
);
|