mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
2568 lines
83 KiB
TypeScript
2568 lines
83 KiB
TypeScript
import DatabaseConfig from "../DatabaseConfig";
|
|
import CreateBy from "../Types/Database/CreateBy";
|
|
import { OnCreate } from "../Types/Database/Hooks";
|
|
import Markdown, { MarkdownContentType } from "../Types/Markdown";
|
|
import CallService from "./CallService";
|
|
import DatabaseService from "./DatabaseService";
|
|
import IncidentService from "./IncidentService";
|
|
import IncidentSeverityService from "./IncidentSeverityService";
|
|
import MailService from "./MailService";
|
|
import ShortLinkService from "./ShortLinkService";
|
|
import SmsService from "./SmsService";
|
|
import WhatsAppService from "./WhatsAppService";
|
|
import UserEmailService from "./UserEmailService";
|
|
import UserOnCallLogService from "./UserOnCallLogService";
|
|
import UserOnCallLogTimelineService from "./UserOnCallLogTimelineService";
|
|
import { AppApiRoute } from "../../ServiceRoute";
|
|
import Hostname from "../../Types/API/Hostname";
|
|
import Protocol from "../../Types/API/Protocol";
|
|
import Route from "../../Types/API/Route";
|
|
import URL from "../../Types/API/URL";
|
|
import CallRequest from "../../Types/Call/CallRequest";
|
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
import OneUptimeDate from "../../Types/Date";
|
|
import Dictionary from "../../Types/Dictionary";
|
|
import Email from "../../Types/Email";
|
|
import EmailMessage from "../../Types/Email/EmailMessage";
|
|
import EmailTemplateType from "../../Types/Email/EmailTemplateType";
|
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
import NotificationRuleType from "../../Types/NotificationRule/NotificationRuleType";
|
|
import ObjectID from "../../Types/ObjectID";
|
|
import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
|
|
import Phone from "../../Types/Phone";
|
|
import SMS from "../../Types/SMS/SMS";
|
|
import WhatsAppMessage from "../../Types/WhatsApp/WhatsAppMessage";
|
|
import {
|
|
renderWhatsAppTemplate,
|
|
WhatsAppTemplateIds,
|
|
WhatsAppTemplateLanguage,
|
|
WhatsAppTemplateId,
|
|
} from "../../Types/WhatsApp/WhatsAppTemplates";
|
|
import UserNotificationEventType from "../../Types/UserNotification/UserNotificationEventType";
|
|
import UserNotificationExecutionStatus from "../../Types/UserNotification/UserNotificationExecutionStatus";
|
|
import UserNotificationStatus from "../../Types/UserNotification/UserNotificationStatus";
|
|
import Incident from "../../Models/DatabaseModels/Incident";
|
|
import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
|
|
import ShortLink from "../../Models/DatabaseModels/ShortLink";
|
|
import UserEmail from "../../Models/DatabaseModels/UserEmail";
|
|
import Model from "../../Models/DatabaseModels/UserNotificationRule";
|
|
import UserOnCallLog from "../../Models/DatabaseModels/UserOnCallLog";
|
|
import UserOnCallLogTimeline from "../../Models/DatabaseModels/UserOnCallLogTimeline";
|
|
import Alert from "../../Models/DatabaseModels/Alert";
|
|
import AlertService from "./AlertService";
|
|
import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
|
|
import AlertSeverityService from "./AlertSeverityService";
|
|
import AlertEpisode from "../../Models/DatabaseModels/AlertEpisode";
|
|
import AlertEpisodeService from "./AlertEpisodeService";
|
|
import AlertEpisodeMember from "../../Models/DatabaseModels/AlertEpisodeMember";
|
|
import AlertEpisodeMemberService from "./AlertEpisodeMemberService";
|
|
import IncidentEpisode from "../../Models/DatabaseModels/IncidentEpisode";
|
|
import IncidentEpisodeService from "./IncidentEpisodeService";
|
|
import WorkspaceNotificationRule from "../../Models/DatabaseModels/WorkspaceNotificationRule";
|
|
import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
|
|
import PushNotificationService from "./PushNotificationService";
|
|
import NotificationRuleEventType from "../../Types/Workspace/NotificationRules/EventType";
|
|
import NotificationRuleWorkspaceChannel from "../../Types/Workspace/NotificationRules/NotificationRuleWorkspaceChannel";
|
|
import PushNotificationUtil from "../Utils/PushNotificationUtil";
|
|
import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
|
|
import logger from "../Utils/Logger";
|
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
|
|
export interface NotificationMethodDescriptor {
|
|
userEmailId?: ObjectID;
|
|
userSmsId?: ObjectID;
|
|
userCallId?: ObjectID;
|
|
userWhatsAppId?: ObjectID;
|
|
userPushId?: ObjectID;
|
|
}
|
|
|
|
export class Service extends DatabaseService<Model> {
|
|
public constructor() {
|
|
super(Model);
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async executeNotificationRuleItem(
|
|
userNotificationRuleId: ObjectID,
|
|
options: {
|
|
projectId: ObjectID;
|
|
triggeredByIncidentId?: ObjectID | undefined;
|
|
triggeredByAlertId?: ObjectID | undefined;
|
|
triggeredByAlertEpisodeId?: ObjectID | undefined;
|
|
triggeredByIncidentEpisodeId?: ObjectID | undefined;
|
|
userNotificationEventType: UserNotificationEventType;
|
|
onCallPolicyExecutionLogId?: ObjectID | undefined;
|
|
onCallPolicyId: ObjectID | undefined;
|
|
onCallPolicyEscalationRuleId?: ObjectID | undefined;
|
|
userNotificationLogId: ObjectID;
|
|
userBelongsToTeamId?: ObjectID | undefined;
|
|
onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined;
|
|
onCallScheduleId?: ObjectID | undefined;
|
|
},
|
|
): Promise<void> {
|
|
// get user notification log and see if this rule has already been executed. If so then skip.
|
|
|
|
const userOnCallLog: UserOnCallLog | null =
|
|
await UserOnCallLogService.findOneById({
|
|
id: options.userNotificationLogId,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
executedNotificationRules: true,
|
|
},
|
|
});
|
|
|
|
if (!userOnCallLog) {
|
|
throw new BadDataException("User notification log not found.");
|
|
}
|
|
|
|
if (
|
|
Object.keys(userOnCallLog.executedNotificationRules || {}).includes(
|
|
userNotificationRuleId.toString(),
|
|
)
|
|
) {
|
|
// already executed.
|
|
return;
|
|
}
|
|
|
|
if (!userOnCallLog.executedNotificationRules) {
|
|
userOnCallLog.executedNotificationRules = {};
|
|
}
|
|
|
|
userOnCallLog.executedNotificationRules[userNotificationRuleId.toString()] =
|
|
OneUptimeDate.getCurrentDate();
|
|
|
|
await UserOnCallLogService.updateOneById({
|
|
id: userOnCallLog.id!,
|
|
data: {
|
|
executedNotificationRules: {
|
|
...userOnCallLog.executedNotificationRules,
|
|
},
|
|
} as any,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// find notification rule item.
|
|
const notificationRuleItem: Model | null = await this.findOneById({
|
|
id: userNotificationRuleId!,
|
|
select: {
|
|
_id: true,
|
|
userId: true,
|
|
userCall: {
|
|
phone: true,
|
|
isVerified: true,
|
|
},
|
|
userSms: {
|
|
phone: true,
|
|
isVerified: true,
|
|
},
|
|
userWhatsApp: {
|
|
phone: true,
|
|
isVerified: true,
|
|
},
|
|
userEmail: {
|
|
email: true,
|
|
isVerified: true,
|
|
},
|
|
userPush: {
|
|
deviceToken: true,
|
|
deviceType: true,
|
|
isVerified: true,
|
|
},
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!notificationRuleItem) {
|
|
throw new BadDataException("Notification rule item not found.");
|
|
}
|
|
|
|
const logTimelineItem: UserOnCallLogTimeline = new UserOnCallLogTimeline();
|
|
logTimelineItem.projectId = options.projectId;
|
|
logTimelineItem.userNotificationLogId = options.userNotificationLogId;
|
|
logTimelineItem.userNotificationRuleId = userNotificationRuleId;
|
|
logTimelineItem.userNotificationLogId = options.userNotificationLogId;
|
|
logTimelineItem.userId = notificationRuleItem.userId!;
|
|
logTimelineItem.userNotificationEventType =
|
|
options.userNotificationEventType;
|
|
|
|
if (options.userBelongsToTeamId) {
|
|
logTimelineItem.userBelongsToTeamId = options.userBelongsToTeamId;
|
|
}
|
|
|
|
if (options.onCallPolicyId) {
|
|
logTimelineItem.onCallDutyPolicyId = options.onCallPolicyId;
|
|
}
|
|
|
|
if (options.onCallPolicyEscalationRuleId) {
|
|
logTimelineItem.onCallDutyPolicyEscalationRuleId =
|
|
options.onCallPolicyEscalationRuleId;
|
|
}
|
|
|
|
if (options.onCallPolicyExecutionLogId) {
|
|
logTimelineItem.onCallDutyPolicyExecutionLogId =
|
|
options.onCallPolicyExecutionLogId;
|
|
}
|
|
|
|
if (options.triggeredByIncidentId) {
|
|
logTimelineItem.triggeredByIncidentId = options.triggeredByIncidentId;
|
|
}
|
|
|
|
if (options.triggeredByAlertId) {
|
|
logTimelineItem.triggeredByAlertId = options.triggeredByAlertId;
|
|
}
|
|
|
|
if (options.triggeredByAlertEpisodeId) {
|
|
logTimelineItem.triggeredByAlertEpisodeId =
|
|
options.triggeredByAlertEpisodeId;
|
|
}
|
|
|
|
if (options.triggeredByIncidentEpisodeId) {
|
|
logTimelineItem.triggeredByIncidentEpisodeId =
|
|
options.triggeredByIncidentEpisodeId;
|
|
}
|
|
|
|
if (options.onCallDutyPolicyExecutionLogTimelineId) {
|
|
logTimelineItem.onCallDutyPolicyExecutionLogTimelineId =
|
|
options.onCallDutyPolicyExecutionLogTimelineId;
|
|
}
|
|
|
|
// add status and status message and save.
|
|
|
|
let incident: Incident | null = null;
|
|
let alert: Alert | null = null;
|
|
let alertEpisode: AlertEpisode | null = null;
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
options.triggeredByIncidentId
|
|
) {
|
|
incident = await IncidentService.findOneById({
|
|
id: options.triggeredByIncidentId!,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
description: true,
|
|
projectId: true,
|
|
project: {
|
|
name: true,
|
|
},
|
|
currentIncidentState: {
|
|
name: true,
|
|
},
|
|
incidentSeverity: {
|
|
name: true,
|
|
},
|
|
rootCause: true,
|
|
incidentNumber: true,
|
|
incidentNumberWithPrefix: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
options.triggeredByAlertId
|
|
) {
|
|
alert = await AlertService.findOneById({
|
|
id: options.triggeredByAlertId!,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
description: true,
|
|
projectId: true,
|
|
project: {
|
|
name: true,
|
|
},
|
|
currentAlertState: {
|
|
name: true,
|
|
},
|
|
alertSeverity: {
|
|
name: true,
|
|
},
|
|
alertNumber: true,
|
|
alertNumberWithPrefix: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
options.triggeredByAlertEpisodeId
|
|
) {
|
|
alertEpisode = await AlertEpisodeService.findOneById({
|
|
id: options.triggeredByAlertEpisodeId!,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
description: true,
|
|
projectId: true,
|
|
project: {
|
|
name: true,
|
|
},
|
|
currentAlertState: {
|
|
name: true,
|
|
},
|
|
alertSeverity: {
|
|
name: true,
|
|
},
|
|
episodeNumber: true,
|
|
episodeNumberWithPrefix: true,
|
|
rootCause: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
let incidentEpisode: IncidentEpisode | null = null;
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentEpisodeCreated &&
|
|
options.triggeredByIncidentEpisodeId
|
|
) {
|
|
incidentEpisode = await IncidentEpisodeService.findOneById({
|
|
id: options.triggeredByIncidentEpisodeId!,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
description: true,
|
|
projectId: true,
|
|
project: {
|
|
name: true,
|
|
},
|
|
currentIncidentState: {
|
|
name: true,
|
|
},
|
|
incidentSeverity: {
|
|
name: true,
|
|
},
|
|
episodeNumber: true,
|
|
episodeNumberWithPrefix: true,
|
|
rootCause: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (!incident && !alert && !alertEpisode && !incidentEpisode) {
|
|
throw new BadDataException(
|
|
"Incident, Alert, Alert Episode, or Incident Episode not found.",
|
|
);
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userEmail?.email &&
|
|
notificationRuleItem.userEmail?.isVerified
|
|
) {
|
|
// send email for alert.
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
alert
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending email to ${notificationRuleItem.userEmail?.email.toString()}`;
|
|
logTimelineItem.userEmailId = notificationRuleItem.userEmail.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const emailMessage: EmailMessage =
|
|
await this.generateEmailTemplateForAlertCreated(
|
|
notificationRuleItem.userEmail?.email,
|
|
alert,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send email.
|
|
|
|
MailService.sendMail(emailMessage, {
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
projectId: options.projectId,
|
|
alertId: alert.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending email.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send email for incident
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
incident
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending email to ${notificationRuleItem.userEmail?.email.toString()}`;
|
|
logTimelineItem.userEmailId = notificationRuleItem.userEmail.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const emailMessage: EmailMessage =
|
|
await this.generateEmailTemplateForIncidentCreated(
|
|
notificationRuleItem.userEmail?.email,
|
|
incident,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send email.
|
|
|
|
MailService.sendMail(emailMessage, {
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
projectId: options.projectId,
|
|
incidentId: incident.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending email.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send email for alert episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
alertEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending email to ${notificationRuleItem.userEmail?.email.toString()}`;
|
|
logTimelineItem.userEmailId = notificationRuleItem.userEmail.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const emailMessage: EmailMessage =
|
|
await this.generateEmailTemplateForAlertEpisodeCreated(
|
|
notificationRuleItem.userEmail?.email,
|
|
alertEpisode,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
MailService.sendMail(emailMessage, {
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
projectId: options.projectId,
|
|
alertEpisodeId: alertEpisode.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending email.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// if you have an email but is not verified, then create a log.
|
|
if (
|
|
notificationRuleItem.userEmail?.email &&
|
|
!notificationRuleItem.userEmail?.isVerified
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Error;
|
|
logTimelineItem.statusMessage = `Email notification not sent because email ${notificationRuleItem.userEmail?.email.toString()} is not verified.`;
|
|
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// send sms.
|
|
if (
|
|
notificationRuleItem.userSms?.phone &&
|
|
notificationRuleItem.userSms?.isVerified
|
|
) {
|
|
//send sms for alert
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
alert
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending SMS to ${notificationRuleItem.userSms?.phone.toString()}.`;
|
|
logTimelineItem.userSmsId = notificationRuleItem.userSms.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const smsMessage: SMS = await this.generateSmsTemplateForAlertCreated(
|
|
notificationRuleItem.userSms.phone,
|
|
alert,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send sms.
|
|
|
|
SmsService.sendSms(smsMessage, {
|
|
projectId: alert.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertId: alert.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending SMS.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send sms for incident
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
incident
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending SMS to ${notificationRuleItem.userSms?.phone.toString()}.`;
|
|
logTimelineItem.userSmsId = notificationRuleItem.userSms.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const smsMessage: SMS =
|
|
await this.generateSmsTemplateForIncidentCreated(
|
|
notificationRuleItem.userSms.phone,
|
|
incident,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send sms.
|
|
|
|
SmsService.sendSms(smsMessage, {
|
|
projectId: incident.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
incidentId: incident.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending SMS.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send sms for alert episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
alertEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending SMS to ${notificationRuleItem.userSms?.phone.toString()}.`;
|
|
logTimelineItem.userSmsId = notificationRuleItem.userSms.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const smsMessage: SMS =
|
|
await this.generateSmsTemplateForAlertEpisodeCreated(
|
|
notificationRuleItem.userSms.phone,
|
|
alertEpisode,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
SmsService.sendSms(smsMessage, {
|
|
projectId: alertEpisode.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertEpisodeId: alertEpisode.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending SMS.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userSms?.phone &&
|
|
!notificationRuleItem.userSms?.isVerified
|
|
) {
|
|
// create a log.
|
|
logTimelineItem.status = UserNotificationStatus.Error;
|
|
logTimelineItem.statusMessage = `SMS not sent because phone ${notificationRuleItem.userSms?.phone.toString()} is not verified.`;
|
|
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userWhatsApp?.phone &&
|
|
notificationRuleItem.userWhatsApp?.isVerified
|
|
) {
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
alert
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending WhatsApp message to ${notificationRuleItem.userWhatsApp?.phone.toString()}.`;
|
|
logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const whatsAppMessage: WhatsAppMessage =
|
|
await this.generateWhatsAppTemplateForAlertCreated(
|
|
notificationRuleItem.userWhatsApp.phone,
|
|
alert,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
WhatsAppService.sendWhatsAppMessage(whatsAppMessage, {
|
|
projectId: alert.projectId,
|
|
alertId: alert.id!,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending WhatsApp message.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
incident
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending WhatsApp message to ${notificationRuleItem.userWhatsApp?.phone.toString()}.`;
|
|
logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const whatsAppMessage: WhatsAppMessage =
|
|
await this.generateWhatsAppTemplateForIncidentCreated(
|
|
notificationRuleItem.userWhatsApp.phone,
|
|
incident,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
WhatsAppService.sendWhatsAppMessage(whatsAppMessage, {
|
|
projectId: incident.projectId,
|
|
incidentId: incident.id!,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending WhatsApp message.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send WhatsApp for alert episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
alertEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending WhatsApp message to ${notificationRuleItem.userWhatsApp?.phone.toString()}.`;
|
|
logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const whatsAppMessage: WhatsAppMessage =
|
|
await this.generateWhatsAppTemplateForAlertEpisodeCreated(
|
|
notificationRuleItem.userWhatsApp.phone,
|
|
alertEpisode,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
WhatsAppService.sendWhatsAppMessage(whatsAppMessage, {
|
|
projectId: alertEpisode.projectId,
|
|
alertEpisodeId: alertEpisode.id!,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending WhatsApp message.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userWhatsApp?.phone &&
|
|
!notificationRuleItem.userWhatsApp?.isVerified
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Error;
|
|
logTimelineItem.statusMessage = `WhatsApp message not sent because phone ${notificationRuleItem.userWhatsApp?.phone.toString()} is not verified.`;
|
|
logTimelineItem.userWhatsAppId = notificationRuleItem.userWhatsApp.id!;
|
|
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// send call.
|
|
if (
|
|
notificationRuleItem.userCall?.phone &&
|
|
notificationRuleItem.userCall?.isVerified
|
|
) {
|
|
// send call for alert
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
alert
|
|
) {
|
|
// create an error log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Making a call to ${notificationRuleItem.userCall?.phone.toString()}.`;
|
|
logTimelineItem.userCallId = notificationRuleItem.userCall.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const callRequest: CallRequest =
|
|
await this.generateCallTemplateForAlertCreated(
|
|
notificationRuleItem.userCall?.phone,
|
|
alert,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send call.
|
|
|
|
CallService.makeCall(callRequest, {
|
|
projectId: alert.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertId: alert.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error making call.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
incident
|
|
) {
|
|
// send call for incident
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Making a call to ${notificationRuleItem.userCall?.phone.toString()}.`;
|
|
logTimelineItem.userCallId = notificationRuleItem.userCall.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const callRequest: CallRequest =
|
|
await this.generateCallTemplateForIncidentCreated(
|
|
notificationRuleItem.userCall?.phone,
|
|
incident,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
// send call.
|
|
|
|
CallService.makeCall(callRequest, {
|
|
projectId: incident.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
incidentId: incident.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error making call.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send call for alert episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
alertEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Making a call to ${notificationRuleItem.userCall?.phone.toString()}.`;
|
|
logTimelineItem.userCallId = notificationRuleItem.userCall.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const callRequest: CallRequest =
|
|
await this.generateCallTemplateForAlertEpisodeCreated(
|
|
notificationRuleItem.userCall?.phone,
|
|
alertEpisode,
|
|
updatedLog.id!,
|
|
);
|
|
|
|
CallService.makeCall(callRequest, {
|
|
projectId: alertEpisode.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertEpisodeId: alertEpisode.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
}).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error making call.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userCall?.phone &&
|
|
!notificationRuleItem.userCall?.isVerified
|
|
) {
|
|
// create a log.
|
|
logTimelineItem.status = UserNotificationStatus.Error;
|
|
logTimelineItem.statusMessage = `Call not sent because phone ${notificationRuleItem.userCall?.phone.toString()} is not verified.`;
|
|
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
// send push notification.
|
|
if (
|
|
notificationRuleItem.userPush?.deviceToken &&
|
|
notificationRuleItem.userPush?.isVerified
|
|
) {
|
|
// send push notification for alert
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertCreated &&
|
|
alert
|
|
) {
|
|
// create a log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending push notification to device.`;
|
|
logTimelineItem.userPushId = notificationRuleItem.userPush.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const pushMessage: PushNotificationMessage =
|
|
PushNotificationUtil.createAlertCreatedNotification({
|
|
alertTitle: alert.title!,
|
|
projectName: alert.project?.name || "OneUptime",
|
|
alertViewLink: (
|
|
await AlertService.getAlertLinkInDashboard(
|
|
alert.projectId!,
|
|
alert.id!,
|
|
)
|
|
).toString(),
|
|
...(alert.alertNumber !== undefined && {
|
|
alertNumber: alert.alertNumber,
|
|
}),
|
|
...(alert.alertNumberWithPrefix && {
|
|
alertNumberWithPrefix: alert.alertNumberWithPrefix,
|
|
}),
|
|
alertId: alert.id!.toString(),
|
|
projectId: alert.projectId!.toString(),
|
|
});
|
|
|
|
// send push notification.
|
|
PushNotificationService.sendPushNotification(
|
|
{
|
|
devices: [
|
|
{
|
|
token: notificationRuleItem.userPush.deviceToken!,
|
|
...(notificationRuleItem.userPush.deviceName && {
|
|
name: notificationRuleItem.userPush.deviceName,
|
|
}),
|
|
},
|
|
],
|
|
message: pushMessage,
|
|
deviceType: notificationRuleItem.userPush
|
|
.deviceType! as PushDeviceType,
|
|
},
|
|
{
|
|
projectId: options.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertId: alert.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
},
|
|
).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending push notification.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send push notification for incident
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentCreated &&
|
|
incident
|
|
) {
|
|
// create a log.
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending push notification to device.`;
|
|
logTimelineItem.userPushId = notificationRuleItem.userPush.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const pushMessage: PushNotificationMessage =
|
|
PushNotificationUtil.createIncidentCreatedNotification({
|
|
incidentTitle: incident.title!,
|
|
projectName: incident.project?.name || "OneUptime",
|
|
incidentViewLink: (
|
|
await IncidentService.getIncidentLinkInDashboard(
|
|
incident.projectId!,
|
|
incident.id!,
|
|
)
|
|
).toString(),
|
|
...(incident.incidentNumber !== undefined && {
|
|
incidentNumber: incident.incidentNumber,
|
|
}),
|
|
...(incident.incidentNumberWithPrefix && {
|
|
incidentNumberWithPrefix: incident.incidentNumberWithPrefix,
|
|
}),
|
|
incidentId: incident.id!.toString(),
|
|
projectId: incident.projectId!.toString(),
|
|
});
|
|
|
|
// send push notification.
|
|
PushNotificationService.sendPushNotification(
|
|
{
|
|
devices: [
|
|
{
|
|
token: notificationRuleItem.userPush.deviceToken!,
|
|
...(notificationRuleItem.userPush.deviceName && {
|
|
name: notificationRuleItem.userPush.deviceName,
|
|
}),
|
|
},
|
|
],
|
|
message: pushMessage,
|
|
deviceType: notificationRuleItem.userPush
|
|
.deviceType! as PushDeviceType,
|
|
},
|
|
{
|
|
projectId: options.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
incidentId: incident.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
},
|
|
).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending push notification.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send push notification for alert episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.AlertEpisodeCreated &&
|
|
alertEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending push notification to device.`;
|
|
logTimelineItem.userPushId = notificationRuleItem.userPush.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const pushMessage: PushNotificationMessage =
|
|
PushNotificationUtil.createAlertEpisodeCreatedNotification({
|
|
alertEpisodeTitle: alertEpisode.title!,
|
|
projectName: alertEpisode.project?.name || "OneUptime",
|
|
alertEpisodeViewLink: (
|
|
await AlertEpisodeService.getEpisodeLinkInDashboard(
|
|
alertEpisode.projectId!,
|
|
alertEpisode.id!,
|
|
)
|
|
).toString(),
|
|
...(alertEpisode.episodeNumber !== undefined && {
|
|
episodeNumber: alertEpisode.episodeNumber,
|
|
}),
|
|
...(alertEpisode.episodeNumberWithPrefix && {
|
|
episodeNumberWithPrefix: alertEpisode.episodeNumberWithPrefix,
|
|
}),
|
|
alertEpisodeId: alertEpisode.id!.toString(),
|
|
projectId: alertEpisode.projectId!.toString(),
|
|
});
|
|
|
|
PushNotificationService.sendPushNotification(
|
|
{
|
|
devices: [
|
|
{
|
|
token: notificationRuleItem.userPush.deviceToken!,
|
|
...(notificationRuleItem.userPush.deviceName && {
|
|
name: notificationRuleItem.userPush.deviceName,
|
|
}),
|
|
},
|
|
],
|
|
message: pushMessage,
|
|
deviceType: notificationRuleItem.userPush
|
|
.deviceType! as PushDeviceType,
|
|
},
|
|
{
|
|
projectId: options.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
alertEpisodeId: alertEpisode.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
},
|
|
).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending push notification.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
// send push notification for incident episode
|
|
if (
|
|
options.userNotificationEventType ===
|
|
UserNotificationEventType.IncidentEpisodeCreated &&
|
|
incidentEpisode
|
|
) {
|
|
logTimelineItem.status = UserNotificationStatus.Sending;
|
|
logTimelineItem.statusMessage = `Sending push notification to device.`;
|
|
logTimelineItem.userPushId = notificationRuleItem.userPush.id!;
|
|
|
|
const updatedLog: UserOnCallLogTimeline =
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const pushMessage: PushNotificationMessage =
|
|
PushNotificationUtil.createIncidentEpisodeCreatedNotification({
|
|
incidentEpisodeTitle: incidentEpisode.title!,
|
|
projectName: incidentEpisode.project?.name || "OneUptime",
|
|
incidentEpisodeViewLink: (
|
|
await IncidentEpisodeService.getEpisodeLinkInDashboard(
|
|
incidentEpisode.projectId!,
|
|
incidentEpisode.id!,
|
|
)
|
|
).toString(),
|
|
...(incidentEpisode.episodeNumber !== undefined && {
|
|
episodeNumber: incidentEpisode.episodeNumber,
|
|
}),
|
|
...(incidentEpisode.episodeNumberWithPrefix && {
|
|
episodeNumberWithPrefix: incidentEpisode.episodeNumberWithPrefix,
|
|
}),
|
|
incidentEpisodeId: incidentEpisode.id!.toString(),
|
|
projectId: incidentEpisode.projectId!.toString(),
|
|
});
|
|
|
|
PushNotificationService.sendPushNotification(
|
|
{
|
|
devices: [
|
|
{
|
|
token: notificationRuleItem.userPush.deviceToken!,
|
|
...(notificationRuleItem.userPush.deviceName && {
|
|
name: notificationRuleItem.userPush.deviceName,
|
|
}),
|
|
},
|
|
],
|
|
message: pushMessage,
|
|
deviceType: notificationRuleItem.userPush
|
|
.deviceType! as PushDeviceType,
|
|
},
|
|
{
|
|
projectId: options.projectId,
|
|
userOnCallLogTimelineId: updatedLog.id!,
|
|
userId: notificationRuleItem.userId!,
|
|
onCallPolicyId: options.onCallPolicyId,
|
|
onCallPolicyEscalationRuleId: options.onCallPolicyEscalationRuleId,
|
|
teamId: options.userBelongsToTeamId,
|
|
onCallDutyPolicyExecutionLogTimelineId:
|
|
options.onCallDutyPolicyExecutionLogTimelineId,
|
|
onCallScheduleId: options.onCallScheduleId,
|
|
},
|
|
).catch(async (err: Error) => {
|
|
await UserOnCallLogTimelineService.updateOneById({
|
|
id: updatedLog.id!,
|
|
data: {
|
|
status: UserNotificationStatus.Error,
|
|
statusMessage: err.message || "Error sending push notification.",
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (
|
|
notificationRuleItem.userPush?.deviceToken &&
|
|
!notificationRuleItem.userPush?.isVerified
|
|
) {
|
|
// create a log.
|
|
logTimelineItem.status = UserNotificationStatus.Error;
|
|
logTimelineItem.statusMessage = `Push notification not sent because device is not verified.`;
|
|
|
|
await UserOnCallLogTimelineService.create({
|
|
data: logTimelineItem,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateCallTemplateForAlertCreated(
|
|
to: Phone,
|
|
alert: Alert,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<CallRequest> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const alertIdentifier: string =
|
|
alert.alertNumber !== undefined
|
|
? `Alert number ${alert.alertNumber}, ${alert.title || "Alert"}`
|
|
: alert.title || "Alert";
|
|
|
|
const callRequest: CallRequest = {
|
|
to: to,
|
|
data: [
|
|
{
|
|
sayMessage: "This is a call from OneUptime",
|
|
},
|
|
{
|
|
sayMessage: "A new alert has been created",
|
|
},
|
|
{
|
|
sayMessage: alertIdentifier,
|
|
},
|
|
{
|
|
introMessage: "To acknowledge this alert press 1",
|
|
numDigits: 1,
|
|
timeoutInSeconds: 10,
|
|
noInputMessage: "You have not entered any input. Good bye",
|
|
onInputCallRequest: {
|
|
"1": {
|
|
sayMessage: "You have acknowledged this alert. Good bye",
|
|
},
|
|
default: {
|
|
sayMessage: "Invalid input. Good bye",
|
|
},
|
|
},
|
|
responseUrl: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/call/gather-input/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
},
|
|
],
|
|
};
|
|
|
|
return callRequest;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateCallTemplateForIncidentCreated(
|
|
to: Phone,
|
|
incident: Incident,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<CallRequest> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const incidentIdentifier: string =
|
|
incident.incidentNumber !== undefined
|
|
? `Incident number ${incident.incidentNumberWithPrefix || incident.incidentNumber}, ${incident.title || "Incident"}`
|
|
: incident.title || "Incident";
|
|
|
|
const callRequest: CallRequest = {
|
|
to: to,
|
|
data: [
|
|
{
|
|
sayMessage: "This is a call from OneUptime",
|
|
},
|
|
{
|
|
sayMessage: "A new incident has been created",
|
|
},
|
|
{
|
|
sayMessage: incidentIdentifier,
|
|
},
|
|
{
|
|
introMessage: "To acknowledge this incident press 1",
|
|
numDigits: 1,
|
|
timeoutInSeconds: 10,
|
|
noInputMessage: "You have not entered any input. Good bye",
|
|
onInputCallRequest: {
|
|
"1": {
|
|
sayMessage: "You have acknowledged this incident. Good bye",
|
|
},
|
|
default: {
|
|
sayMessage: "Invalid input. Good bye",
|
|
},
|
|
},
|
|
responseUrl: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/call/gather-input/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
},
|
|
],
|
|
};
|
|
|
|
return callRequest;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateCallTemplateForAlertEpisodeCreated(
|
|
to: Phone,
|
|
alertEpisode: AlertEpisode,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<CallRequest> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const episodeIdentifier: string = alertEpisode.episodeNumberWithPrefix
|
|
? `Alert episode ${alertEpisode.episodeNumberWithPrefix}, ${alertEpisode.title || "Alert Episode"}`
|
|
: alertEpisode.episodeNumber !== undefined
|
|
? `Alert episode number ${alertEpisode.episodeNumber}, ${alertEpisode.title || "Alert Episode"}`
|
|
: alertEpisode.title || "Alert Episode";
|
|
|
|
const callRequest: CallRequest = {
|
|
to: to,
|
|
data: [
|
|
{
|
|
sayMessage: "This is a call from OneUptime",
|
|
},
|
|
{
|
|
sayMessage: "A new alert episode has been created",
|
|
},
|
|
{
|
|
sayMessage: episodeIdentifier,
|
|
},
|
|
{
|
|
introMessage: "To acknowledge this alert episode press 1",
|
|
numDigits: 1,
|
|
timeoutInSeconds: 10,
|
|
noInputMessage: "You have not entered any input. Good bye",
|
|
onInputCallRequest: {
|
|
"1": {
|
|
sayMessage: "You have acknowledged this alert episode. Good bye",
|
|
},
|
|
default: {
|
|
sayMessage: "Invalid input. Good bye",
|
|
},
|
|
},
|
|
responseUrl: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/call/gather-input/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
},
|
|
],
|
|
};
|
|
|
|
return callRequest;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateSmsTemplateForAlertCreated(
|
|
to: Phone,
|
|
alert: Alert,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<SMS> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const shortUrl: ShortLink = await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
),
|
|
);
|
|
const url: URL = await ShortLinkService.getShortenedUrl(shortUrl);
|
|
|
|
const alertIdentifier: string =
|
|
alert.alertNumber !== undefined
|
|
? `${alert.alertNumberWithPrefix || "#" + alert.alertNumber} (${alert.title || "Alert"})`
|
|
: alert.title || "Alert";
|
|
|
|
const sms: SMS = {
|
|
to,
|
|
message: `This is a message from OneUptime. A new alert has been created: ${alertIdentifier}. To acknowledge this alert, please click on the following link ${url.toString()}`,
|
|
};
|
|
|
|
return sms;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateSmsTemplateForIncidentCreated(
|
|
to: Phone,
|
|
incident: Incident,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<SMS> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const shortUrl: ShortLink = await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
),
|
|
);
|
|
const url: URL = await ShortLinkService.getShortenedUrl(shortUrl);
|
|
|
|
const incidentIdentifier: string =
|
|
incident.incidentNumber !== undefined
|
|
? `${incident.incidentNumberWithPrefix || "#" + incident.incidentNumber} (${incident.title || "Incident"})`
|
|
: incident.title || "Incident";
|
|
|
|
const sms: SMS = {
|
|
to,
|
|
message: `This is a message from OneUptime. A new incident has been created: ${incidentIdentifier}. To acknowledge this incident, please click on the following link ${url.toString()}`,
|
|
};
|
|
|
|
return sms;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateSmsTemplateForAlertEpisodeCreated(
|
|
to: Phone,
|
|
alertEpisode: AlertEpisode,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<SMS> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const shortUrl: ShortLink = await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
),
|
|
);
|
|
const url: URL = await ShortLinkService.getShortenedUrl(shortUrl);
|
|
|
|
const episodeIdentifier: string = alertEpisode.episodeNumberWithPrefix
|
|
? `${alertEpisode.episodeNumberWithPrefix} (${alertEpisode.title || "Alert Episode"})`
|
|
: alertEpisode.episodeNumber !== undefined
|
|
? `#${alertEpisode.episodeNumber} (${alertEpisode.title || "Alert Episode"})`
|
|
: alertEpisode.title || "Alert Episode";
|
|
|
|
const sms: SMS = {
|
|
to,
|
|
message: `This is a message from OneUptime. A new alert episode has been created: ${episodeIdentifier}. To acknowledge this alert episode, please click on the following link ${url.toString()}`,
|
|
};
|
|
|
|
return sms;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateWhatsAppTemplateForAlertCreated(
|
|
to: Phone,
|
|
alert: Alert,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<WhatsAppMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const acknowledgeShortLink: ShortLink =
|
|
await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/acknowledge-page/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
);
|
|
|
|
const acknowledgeUrl: URL =
|
|
await ShortLinkService.getShortenedUrl(acknowledgeShortLink);
|
|
|
|
const alertLinkOnDashboard: string =
|
|
alert.projectId && alert.id
|
|
? (
|
|
await AlertService.getAlertLinkInDashboard(
|
|
alert.projectId,
|
|
alert.id,
|
|
)
|
|
).toString()
|
|
: acknowledgeUrl.toString();
|
|
|
|
const templateKey: WhatsAppTemplateId = WhatsAppTemplateIds.AlertCreated;
|
|
const templateVariables: Record<string, string> = {
|
|
project_name: alert.project?.name || "OneUptime",
|
|
alert_title: alert.title || "",
|
|
acknowledge_url: acknowledgeUrl.toString(),
|
|
alert_number:
|
|
alert.alertNumber !== undefined ? alert.alertNumber.toString() : "",
|
|
alert_link: alertLinkOnDashboard,
|
|
};
|
|
|
|
const body: string = renderWhatsAppTemplate(templateKey, templateVariables);
|
|
|
|
return {
|
|
to,
|
|
body,
|
|
templateKey,
|
|
templateVariables,
|
|
templateLanguageCode: WhatsAppTemplateLanguage[templateKey],
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateWhatsAppTemplateForIncidentCreated(
|
|
to: Phone,
|
|
incident: Incident,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<WhatsAppMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const acknowledgeShortLink: ShortLink =
|
|
await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/acknowledge-page/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
);
|
|
|
|
const acknowledgeUrl: URL =
|
|
await ShortLinkService.getShortenedUrl(acknowledgeShortLink);
|
|
|
|
const incidentLinkOnDashboard: string =
|
|
incident.projectId && incident.id
|
|
? (
|
|
await IncidentService.getIncidentLinkInDashboard(
|
|
incident.projectId,
|
|
incident.id,
|
|
)
|
|
).toString()
|
|
: acknowledgeUrl.toString();
|
|
|
|
const templateKey: WhatsAppTemplateId = WhatsAppTemplateIds.IncidentCreated;
|
|
const templateVariables: Record<string, string> = {
|
|
project_name: incident.project?.name || "OneUptime",
|
|
incident_title: incident.title || "",
|
|
acknowledge_url: acknowledgeUrl.toString(),
|
|
incident_number:
|
|
incident.incidentNumber !== undefined
|
|
? incident.incidentNumber.toString()
|
|
: "",
|
|
incident_link: incidentLinkOnDashboard,
|
|
};
|
|
|
|
const body: string = renderWhatsAppTemplate(templateKey, templateVariables);
|
|
|
|
return {
|
|
to,
|
|
body,
|
|
templateKey,
|
|
templateVariables,
|
|
templateLanguageCode: WhatsAppTemplateLanguage[templateKey],
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateWhatsAppTemplateForAlertEpisodeCreated(
|
|
to: Phone,
|
|
alertEpisode: AlertEpisode,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<WhatsAppMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const acknowledgeShortLink: ShortLink =
|
|
await ShortLinkService.saveShortLinkFor(
|
|
new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute(
|
|
"/acknowledge-page/" + userOnCallLogTimelineId.toString(),
|
|
),
|
|
),
|
|
);
|
|
|
|
const acknowledgeUrl: URL =
|
|
await ShortLinkService.getShortenedUrl(acknowledgeShortLink);
|
|
|
|
const episodeLinkOnDashboard: string =
|
|
alertEpisode.projectId && alertEpisode.id
|
|
? (
|
|
await AlertEpisodeService.getEpisodeLinkInDashboard(
|
|
alertEpisode.projectId,
|
|
alertEpisode.id,
|
|
)
|
|
).toString()
|
|
: acknowledgeUrl.toString();
|
|
|
|
const templateKey: WhatsAppTemplateId =
|
|
WhatsAppTemplateIds.AlertEpisodeCreated;
|
|
const templateVariables: Record<string, string> = {
|
|
project_name: alertEpisode.project?.name || "OneUptime",
|
|
episode_title: alertEpisode.title || "",
|
|
acknowledge_url: acknowledgeUrl.toString(),
|
|
episode_number:
|
|
alertEpisode.episodeNumberWithPrefix ||
|
|
(alertEpisode.episodeNumber !== undefined
|
|
? alertEpisode.episodeNumber.toString()
|
|
: ""),
|
|
episode_link: episodeLinkOnDashboard,
|
|
};
|
|
|
|
const body: string = renderWhatsAppTemplate(templateKey, templateVariables);
|
|
|
|
return {
|
|
to,
|
|
body,
|
|
templateKey,
|
|
templateVariables,
|
|
templateLanguageCode: WhatsAppTemplateLanguage[templateKey],
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateEmailTemplateForAlertCreated(
|
|
to: Email,
|
|
alert: Alert,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<EmailMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const alertNumber: string =
|
|
alert.alertNumberWithPrefix ||
|
|
(alert.alertNumber ? `#${alert.alertNumber}` : "");
|
|
|
|
const vars: Dictionary<string> = {
|
|
alertTitle: alert.title!,
|
|
alertNumber: alertNumber,
|
|
projectName: alert.project!.name!,
|
|
currentState: alert.currentAlertState!.name!,
|
|
alertDescription: await Markdown.convertToHTML(
|
|
alert.description! || "",
|
|
MarkdownContentType.Email,
|
|
),
|
|
alertSeverity: alert.alertSeverity!.name!,
|
|
alertViewLink: (
|
|
await AlertService.getAlertLinkInDashboard(alert.projectId!, alert.id!)
|
|
).toString(),
|
|
acknowledgeAlertLink: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
).toString(),
|
|
};
|
|
|
|
const emailMessage: EmailMessage = {
|
|
toEmail: to!,
|
|
templateType: EmailTemplateType.AcknowledgeAlert,
|
|
vars: vars,
|
|
subject: `ACTION REQUIRED: Alert ${alertNumber} created - ${alert.title!}`,
|
|
};
|
|
|
|
return emailMessage;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateEmailTemplateForIncidentCreated(
|
|
to: Email,
|
|
incident: Incident,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<EmailMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
const incidentNumber: string =
|
|
incident.incidentNumberWithPrefix ||
|
|
(incident.incidentNumber ? `#${incident.incidentNumber}` : "");
|
|
|
|
const vars: Dictionary<string> = {
|
|
incidentTitle: incident.title!,
|
|
incidentNumber: incidentNumber,
|
|
projectName: incident.project!.name!,
|
|
currentState: incident.currentIncidentState!.name!,
|
|
incidentDescription: await Markdown.convertToHTML(
|
|
incident.description! || "",
|
|
MarkdownContentType.Email,
|
|
),
|
|
incidentSeverity: incident.incidentSeverity!.name!,
|
|
rootCause:
|
|
incident.rootCause || "No root cause identified for this incident",
|
|
incidentViewLink: (
|
|
await IncidentService.getIncidentLinkInDashboard(
|
|
incident.projectId!,
|
|
incident.id!,
|
|
)
|
|
).toString(),
|
|
acknowledgeIncidentLink: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
).toString(),
|
|
};
|
|
|
|
const emailMessage: EmailMessage = {
|
|
toEmail: to!,
|
|
templateType: EmailTemplateType.AcknowledgeIncident,
|
|
vars: vars,
|
|
subject: `ACTION REQUIRED: Incident ${incidentNumber} created - ${incident.title!}`,
|
|
};
|
|
|
|
return emailMessage;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async generateEmailTemplateForAlertEpisodeCreated(
|
|
to: Email,
|
|
alertEpisode: AlertEpisode,
|
|
userOnCallLogTimelineId: ObjectID,
|
|
): Promise<EmailMessage> {
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
// Fetch alerts that are members of this episode
|
|
const episodeMembers: Array<AlertEpisodeMember> =
|
|
await AlertEpisodeMemberService.findBy({
|
|
query: {
|
|
alertEpisodeId: alertEpisode.id!,
|
|
},
|
|
select: {
|
|
alertId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
});
|
|
|
|
// Get the alert IDs
|
|
const alertIds: Array<ObjectID> = episodeMembers
|
|
.map((member: AlertEpisodeMember) => {
|
|
return member.alertId;
|
|
})
|
|
.filter((id: ObjectID | undefined): id is ObjectID => {
|
|
return id !== undefined;
|
|
});
|
|
|
|
// Fetch full alert data with monitors
|
|
const alerts: Array<Alert> =
|
|
alertIds.length > 0
|
|
? await AlertService.findBy({
|
|
query: {
|
|
_id: QueryHelper.any(alertIds),
|
|
},
|
|
select: {
|
|
_id: true,
|
|
title: true,
|
|
alertNumber: true,
|
|
alertNumberWithPrefix: true,
|
|
monitor: {
|
|
_id: true,
|
|
name: true,
|
|
},
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
})
|
|
: [];
|
|
|
|
// Get unique monitors (resources affected)
|
|
const monitorNames: Set<string> = new Set();
|
|
for (const alert of alerts) {
|
|
if (alert.monitor?.name) {
|
|
monitorNames.add(alert.monitor.name);
|
|
}
|
|
}
|
|
|
|
const resourcesAffected: string =
|
|
monitorNames.size > 0
|
|
? Array.from(monitorNames).join(", ")
|
|
: "No resources identified";
|
|
|
|
// Build alerts list HTML with proper email styling
|
|
let alertsListHtml: string = "";
|
|
if (alerts.length > 0) {
|
|
const alertRows: string[] = [];
|
|
for (const alert of alerts) {
|
|
const alertTitle: string = alert.title || "Untitled Alert";
|
|
const alertNumber: string =
|
|
alert.alertNumberWithPrefix ||
|
|
(alert.alertNumber ? `#${alert.alertNumber}` : "");
|
|
const alertLink: string = (
|
|
await AlertService.getAlertLinkInDashboard(
|
|
alertEpisode.projectId!,
|
|
alert.id!,
|
|
)
|
|
).toString();
|
|
const monitorName: string = alert.monitor?.name || "";
|
|
|
|
alertRows.push(`
|
|
<tr>
|
|
<td style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0;">
|
|
<table cellpadding="0" cellspacing="0" width="100%">
|
|
<tr>
|
|
<td style="vertical-align: middle;">
|
|
<span style="display: inline-block; background-color: #dbeafe; color: #1e40af; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; margin-right: 8px;">${alertNumber}</span>
|
|
<a href="${alertLink}" style="color: #2563eb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 500; text-decoration: none;">${alertTitle}</a>
|
|
${monitorName ? `<span style="display: block; color: #64748b; font-size: 12px; margin-top: 4px;">Monitor: ${monitorName}</span>` : ""}
|
|
</td>
|
|
<td style="text-align: right; vertical-align: middle;">
|
|
<a href="${alertLink}" style="color: #2563eb; font-size: 12px; text-decoration: none;">View →</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
`);
|
|
}
|
|
if (alertRows.length > 0) {
|
|
alertsListHtml = `
|
|
<table cellpadding="0" cellspacing="0" width="100%" style="background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 8px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0;">
|
|
<tbody>
|
|
${alertRows.join("")}
|
|
</tbody>
|
|
</table>
|
|
`;
|
|
}
|
|
}
|
|
|
|
const episodeNumber: string =
|
|
alertEpisode.episodeNumberWithPrefix ||
|
|
(alertEpisode.episodeNumber ? `#${alertEpisode.episodeNumber}` : "");
|
|
|
|
const vars: Dictionary<string> = {
|
|
alertEpisodeTitle: alertEpisode.title!,
|
|
episodeNumber: episodeNumber,
|
|
projectName: alertEpisode.project!.name!,
|
|
currentState: alertEpisode.currentAlertState!.name!,
|
|
alertEpisodeDescription: await Markdown.convertToHTML(
|
|
alertEpisode.description! || "",
|
|
MarkdownContentType.Email,
|
|
),
|
|
alertEpisodeSeverity: alertEpisode.alertSeverity!.name!,
|
|
resourcesAffected: resourcesAffected,
|
|
rootCause:
|
|
alertEpisode.rootCause ||
|
|
"No root cause identified for this alert episode",
|
|
alertsList: alertsListHtml,
|
|
alertsCount: alerts.length.toString(),
|
|
alertEpisodeViewLink: (
|
|
await AlertEpisodeService.getEpisodeLinkInDashboard(
|
|
alertEpisode.projectId!,
|
|
alertEpisode.id!,
|
|
)
|
|
).toString(),
|
|
acknowledgeAlertEpisodeLink: new URL(
|
|
httpProtocol,
|
|
host,
|
|
new Route(AppApiRoute.toString())
|
|
.addRoute(new UserOnCallLogTimeline().crudApiPath!)
|
|
.addRoute("/acknowledge-page/" + userOnCallLogTimelineId.toString()),
|
|
).toString(),
|
|
};
|
|
|
|
const emailMessage: EmailMessage = {
|
|
toEmail: to!,
|
|
templateType: EmailTemplateType.AcknowledgeAlertEpisode,
|
|
vars: vars,
|
|
subject: `ACTION REQUIRED: Alert Episode ${episodeNumber} created - ${alertEpisode.title!}`,
|
|
};
|
|
|
|
return emailMessage;
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async startUserNotificationRulesExecution(
|
|
userId: ObjectID,
|
|
options: {
|
|
projectId: ObjectID;
|
|
triggeredByIncidentId?: ObjectID | undefined;
|
|
triggeredByAlertId?: ObjectID | undefined;
|
|
triggeredByAlertEpisodeId?: ObjectID | undefined;
|
|
triggeredByIncidentEpisodeId?: ObjectID | undefined;
|
|
userNotificationEventType: UserNotificationEventType;
|
|
onCallPolicyExecutionLogId?: ObjectID | undefined;
|
|
onCallPolicyId: ObjectID | undefined;
|
|
onCallPolicyEscalationRuleId?: ObjectID | undefined;
|
|
userBelongsToTeamId?: ObjectID | undefined;
|
|
onCallDutyPolicyExecutionLogTimelineId?: ObjectID | undefined;
|
|
onCallScheduleId?: ObjectID | undefined;
|
|
overridedByUserId?: ObjectID | undefined;
|
|
},
|
|
): Promise<void> {
|
|
// add user notification log.
|
|
const userOnCallLog: UserOnCallLog = new UserOnCallLog();
|
|
|
|
userOnCallLog.userId = userId;
|
|
userOnCallLog.projectId = options.projectId;
|
|
|
|
if (options.triggeredByIncidentId) {
|
|
userOnCallLog.triggeredByIncidentId = options.triggeredByIncidentId;
|
|
}
|
|
|
|
if (options.triggeredByAlertId) {
|
|
userOnCallLog.triggeredByAlertId = options.triggeredByAlertId;
|
|
}
|
|
|
|
if (options.triggeredByAlertEpisodeId) {
|
|
userOnCallLog.triggeredByAlertEpisodeId =
|
|
options.triggeredByAlertEpisodeId;
|
|
}
|
|
|
|
if (options.triggeredByIncidentEpisodeId) {
|
|
userOnCallLog.triggeredByIncidentEpisodeId =
|
|
options.triggeredByIncidentEpisodeId;
|
|
}
|
|
|
|
userOnCallLog.userNotificationEventType = options.userNotificationEventType;
|
|
|
|
if (options.onCallPolicyExecutionLogId) {
|
|
userOnCallLog.onCallDutyPolicyExecutionLogId =
|
|
options.onCallPolicyExecutionLogId;
|
|
}
|
|
|
|
if (options.onCallPolicyId) {
|
|
userOnCallLog.onCallDutyPolicyId = options.onCallPolicyId;
|
|
}
|
|
|
|
if (options.onCallDutyPolicyExecutionLogTimelineId) {
|
|
userOnCallLog.onCallDutyPolicyExecutionLogTimelineId =
|
|
options.onCallDutyPolicyExecutionLogTimelineId;
|
|
}
|
|
|
|
if (options.onCallPolicyEscalationRuleId) {
|
|
userOnCallLog.onCallDutyPolicyEscalationRuleId =
|
|
options.onCallPolicyEscalationRuleId;
|
|
}
|
|
|
|
if (options.userBelongsToTeamId) {
|
|
userOnCallLog.userBelongsToTeamId = options.userBelongsToTeamId;
|
|
}
|
|
|
|
if (options.onCallScheduleId) {
|
|
userOnCallLog.onCallDutyScheduleId = options.onCallScheduleId;
|
|
}
|
|
|
|
userOnCallLog.status = UserNotificationExecutionStatus.Scheduled;
|
|
userOnCallLog.statusMessage = "Scheduled";
|
|
|
|
if (options.overridedByUserId) {
|
|
userOnCallLog.overridedByUserId = options.overridedByUserId;
|
|
}
|
|
|
|
await UserOnCallLogService.create({
|
|
data: userOnCallLog,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Alert workspace here. Invite users to channels for example. If they are not invited.
|
|
|
|
this.runWorkspaceRulesForOnCallNotification({
|
|
projectId: options.projectId,
|
|
alertId: options.triggeredByAlertId,
|
|
incidentId: options.triggeredByIncidentId,
|
|
userId: userId,
|
|
}).catch((error: Error) => {
|
|
logger.error(error);
|
|
});
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async runWorkspaceRulesForOnCallNotification(data: {
|
|
projectId: ObjectID;
|
|
incidentId?: ObjectID | undefined;
|
|
alertId?: ObjectID | undefined;
|
|
userId: ObjectID;
|
|
}): Promise<void> {
|
|
// if alert and incidient are both present, then throw an error.
|
|
if (data.incidentId && data.alertId) {
|
|
throw new BadDataException("Either incidentId or alertId is required.");
|
|
}
|
|
|
|
// if none are present, then throw an error.
|
|
|
|
if (!data.incidentId && !data.alertId) {
|
|
throw new BadDataException("Either incidentId or alertId is required.");
|
|
}
|
|
|
|
// get notification rule where inviteOwners is true.
|
|
const notificationRules: Array<WorkspaceNotificationRule> =
|
|
await WorkspaceNotificationRuleService.getNotificationRulesWhereInviteOwnersIsTrue(
|
|
{
|
|
projectId: data.projectId!,
|
|
notificationFor: {
|
|
incidentId: data.incidentId,
|
|
alertId: data.alertId,
|
|
},
|
|
notificationRuleEventType: data.incidentId
|
|
? NotificationRuleEventType.Incident
|
|
: NotificationRuleEventType.Alert,
|
|
},
|
|
);
|
|
|
|
let workspaceChannels: Array<NotificationRuleWorkspaceChannel> = [];
|
|
|
|
if (data.incidentId) {
|
|
workspaceChannels = await IncidentService.getWorkspaceChannelForIncident({
|
|
incidentId: data.incidentId!,
|
|
});
|
|
}
|
|
|
|
if (data.alertId) {
|
|
workspaceChannels = await AlertService.getWorkspaceChannelForAlert({
|
|
alertId: data.alertId!,
|
|
});
|
|
}
|
|
|
|
WorkspaceNotificationRuleService.inviteUsersBasedOnRulesAndWorkspaceChannels(
|
|
{
|
|
notificationRules: notificationRules,
|
|
projectId: data.projectId!,
|
|
workspaceChannels: workspaceChannels,
|
|
userIds: [data.userId],
|
|
},
|
|
).catch((error: Error) => {
|
|
logger.error(error);
|
|
});
|
|
}
|
|
|
|
@CaptureSpan()
|
|
protected override async onBeforeCreate(
|
|
createBy: CreateBy<Model>,
|
|
): Promise<OnCreate<Model>> {
|
|
if (
|
|
!createBy.data.userCallId &&
|
|
!createBy.data.userCall &&
|
|
!createBy.data.userEmail &&
|
|
!createBy.data.userSms &&
|
|
!createBy.data.userSmsId &&
|
|
!createBy.data.userWhatsApp &&
|
|
!createBy.data.userWhatsAppId &&
|
|
!createBy.data.userEmailId &&
|
|
!createBy.data.userPushId &&
|
|
!createBy.data.userPush
|
|
) {
|
|
throw new BadDataException(
|
|
"Call, SMS, WhatsApp, Email, or Push notification is required",
|
|
);
|
|
}
|
|
|
|
return {
|
|
createBy,
|
|
carryForward: null,
|
|
};
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async addDefaultNotificationRulesForVerifiedMethod(data: {
|
|
projectId: ObjectID;
|
|
userId: ObjectID;
|
|
notificationMethod: NotificationMethodDescriptor;
|
|
}): Promise<void> {
|
|
const { projectId, userId, notificationMethod } = data;
|
|
|
|
await this.createIncidentOnCallRules(projectId, userId, notificationMethod);
|
|
await this.createAlertOnCallRules(projectId, userId, notificationMethod);
|
|
await this.createSingleRule(
|
|
projectId,
|
|
userId,
|
|
notificationMethod,
|
|
NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
|
|
);
|
|
await this.createSingleRule(
|
|
projectId,
|
|
userId,
|
|
notificationMethod,
|
|
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
|
|
);
|
|
await this.createSingleRule(
|
|
projectId,
|
|
userId,
|
|
notificationMethod,
|
|
NotificationRuleType.WHEN_USER_GOES_ON_CALL,
|
|
);
|
|
await this.createSingleRule(
|
|
projectId,
|
|
userId,
|
|
notificationMethod,
|
|
NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
|
|
);
|
|
}
|
|
|
|
private applyNotificationMethod(
|
|
rule: Model,
|
|
descriptor: NotificationMethodDescriptor,
|
|
): void {
|
|
if (descriptor.userEmailId) {
|
|
rule.userEmailId = descriptor.userEmailId;
|
|
}
|
|
if (descriptor.userSmsId) {
|
|
rule.userSmsId = descriptor.userSmsId;
|
|
}
|
|
if (descriptor.userCallId) {
|
|
rule.userCallId = descriptor.userCallId;
|
|
}
|
|
if (descriptor.userWhatsAppId) {
|
|
rule.userWhatsAppId = descriptor.userWhatsAppId;
|
|
}
|
|
if (descriptor.userPushId) {
|
|
rule.userPushId = descriptor.userPushId;
|
|
}
|
|
}
|
|
|
|
private getNotificationMethodQuery(
|
|
descriptor: NotificationMethodDescriptor,
|
|
): Record<string, ObjectID> {
|
|
const query: Record<string, ObjectID> = {};
|
|
if (descriptor.userEmailId) {
|
|
query["userEmailId"] = descriptor.userEmailId;
|
|
}
|
|
if (descriptor.userSmsId) {
|
|
query["userSmsId"] = descriptor.userSmsId;
|
|
}
|
|
if (descriptor.userCallId) {
|
|
query["userCallId"] = descriptor.userCallId;
|
|
}
|
|
if (descriptor.userWhatsAppId) {
|
|
query["userWhatsAppId"] = descriptor.userWhatsAppId;
|
|
}
|
|
if (descriptor.userPushId) {
|
|
query["userPushId"] = descriptor.userPushId;
|
|
}
|
|
return query;
|
|
}
|
|
|
|
private async createIncidentOnCallRules(
|
|
projectId: ObjectID,
|
|
userId: ObjectID,
|
|
notificationMethod: NotificationMethodDescriptor,
|
|
): Promise<void> {
|
|
const incidentSeverities: Array<IncidentSeverity> =
|
|
await IncidentSeverityService.findBy({
|
|
query: {
|
|
projectId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
select: {
|
|
_id: true,
|
|
},
|
|
});
|
|
|
|
for (const incidentSeverity of incidentSeverities) {
|
|
const existingRule: Model | null = await this.findOneBy({
|
|
query: {
|
|
projectId,
|
|
userId,
|
|
...this.getNotificationMethodQuery(notificationMethod),
|
|
incidentSeverityId: incidentSeverity.id!,
|
|
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
|
|
} as any,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (existingRule) {
|
|
continue;
|
|
}
|
|
|
|
const rule: Model = new Model();
|
|
rule.projectId = projectId;
|
|
rule.userId = userId;
|
|
this.applyNotificationMethod(rule, notificationMethod);
|
|
rule.incidentSeverityId = incidentSeverity.id!;
|
|
rule.notifyAfterMinutes = 0;
|
|
rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
|
|
|
|
await this.create({
|
|
data: rule,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
private async createAlertOnCallRules(
|
|
projectId: ObjectID,
|
|
userId: ObjectID,
|
|
notificationMethod: NotificationMethodDescriptor,
|
|
): Promise<void> {
|
|
const alertSeverities: Array<AlertSeverity> =
|
|
await AlertSeverityService.findBy({
|
|
query: {
|
|
projectId,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
select: {
|
|
_id: true,
|
|
},
|
|
});
|
|
|
|
for (const alertSeverity of alertSeverities) {
|
|
const existingRule: Model | null = await this.findOneBy({
|
|
query: {
|
|
projectId,
|
|
userId,
|
|
...this.getNotificationMethodQuery(notificationMethod),
|
|
alertSeverityId: alertSeverity.id!,
|
|
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
|
|
} as any,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (existingRule) {
|
|
continue;
|
|
}
|
|
|
|
const rule: Model = new Model();
|
|
rule.projectId = projectId;
|
|
rule.userId = userId;
|
|
this.applyNotificationMethod(rule, notificationMethod);
|
|
rule.alertSeverityId = alertSeverity.id!;
|
|
rule.notifyAfterMinutes = 0;
|
|
rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
|
|
|
|
await this.create({
|
|
data: rule,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
private async createSingleRule(
|
|
projectId: ObjectID,
|
|
userId: ObjectID,
|
|
notificationMethod: NotificationMethodDescriptor,
|
|
ruleType: NotificationRuleType,
|
|
): Promise<void> {
|
|
const existingRule: Model | null = await this.findOneBy({
|
|
query: {
|
|
projectId,
|
|
userId,
|
|
...this.getNotificationMethodQuery(notificationMethod),
|
|
ruleType,
|
|
} as any,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (existingRule) {
|
|
return;
|
|
}
|
|
|
|
const rule: Model = new Model();
|
|
rule.projectId = projectId;
|
|
rule.userId = userId;
|
|
this.applyNotificationMethod(rule, notificationMethod);
|
|
rule.notifyAfterMinutes = 0;
|
|
rule.ruleType = ruleType;
|
|
|
|
await this.create({
|
|
data: rule,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
@CaptureSpan()
|
|
public async addDefaultNotificationRuleForUser(
|
|
projectId: ObjectID,
|
|
userId: ObjectID,
|
|
email: Email,
|
|
): Promise<void> {
|
|
let userEmail: UserEmail | null = await UserEmailService.findOneBy({
|
|
query: {
|
|
projectId,
|
|
userId,
|
|
email,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!userEmail) {
|
|
userEmail = new UserEmail();
|
|
userEmail.projectId = projectId;
|
|
userEmail.userId = userId;
|
|
userEmail.email = email;
|
|
userEmail.isVerified = true;
|
|
|
|
userEmail = await UserEmailService.create({
|
|
data: userEmail,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
await this.addDefaultNotificationRulesForVerifiedMethod({
|
|
projectId,
|
|
userId,
|
|
notificationMethod: {
|
|
userEmailId: userEmail.id!,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
export default new Service();
|