Files
oneuptime/Common/Server/Services/UserNotificationRuleService.ts

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();