mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
557 lines
15 KiB
TypeScript
557 lines
15 KiB
TypeScript
import TeamComplianceSettingService from "./TeamComplianceSettingService";
|
|
import TeamMemberService from "./TeamMemberService";
|
|
import UserEmailService from "./UserEmailService";
|
|
import UserSmsService from "./UserSmsService";
|
|
import UserCallService from "./UserCallService";
|
|
import UserPushService from "./UserPushService";
|
|
import IncidentSeverityService from "./IncidentSeverityService";
|
|
import AlertSeverityService from "./AlertSeverityService";
|
|
import UserNotificationRuleService from "./UserNotificationRuleService";
|
|
import UserService from "./UserService";
|
|
import TeamService from "./TeamService";
|
|
import ObjectID from "../../Types/ObjectID";
|
|
import ComplianceRuleType from "../../Types/Team/ComplianceRuleType";
|
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
import Includes from "../../Types/BaseDatabase/Includes";
|
|
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
|
|
import Team from "../../Models/DatabaseModels/Team";
|
|
import IncidentSeverity from "../../Models/DatabaseModels/IncidentSeverity";
|
|
import AlertSeverity from "../../Models/DatabaseModels/AlertSeverity";
|
|
|
|
export interface UserComplianceStatus {
|
|
userId: ObjectID;
|
|
userName: string;
|
|
userEmail: string;
|
|
userProfilePictureId?: ObjectID | undefined;
|
|
isCompliant: boolean;
|
|
nonCompliantRules: Array<{
|
|
ruleType: ComplianceRuleType;
|
|
reason: string;
|
|
}>;
|
|
}
|
|
|
|
export interface TeamComplianceStatus {
|
|
teamId: ObjectID;
|
|
teamName: string;
|
|
complianceSettings: Array<{
|
|
ruleType: ComplianceRuleType;
|
|
enabled: boolean;
|
|
}>;
|
|
userComplianceStatuses: Array<UserComplianceStatus>;
|
|
}
|
|
|
|
export default class TeamComplianceService {
|
|
public static async getTeamComplianceStatus(
|
|
teamId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<TeamComplianceStatus> {
|
|
// Get team details
|
|
const team: Partial<Team> | null = await TeamService.findOneById({
|
|
id: teamId,
|
|
select: {
|
|
name: true,
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!team) {
|
|
throw new BadDataException("Team not found");
|
|
}
|
|
|
|
// Get compliance settings for this team
|
|
const complianceSettings: Array<{
|
|
ruleType?: ComplianceRuleType;
|
|
enabled?: boolean;
|
|
}> = await TeamComplianceSettingService.findBy({
|
|
query: {
|
|
teamId: teamId,
|
|
projectId: projectId,
|
|
},
|
|
select: {
|
|
ruleType: true,
|
|
enabled: true,
|
|
},
|
|
limit: 100,
|
|
skip: 0,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Get team members
|
|
const teamMembers: Array<{ userId?: ObjectID; _id?: string }> =
|
|
await TeamMemberService.findBy({
|
|
query: {
|
|
teamId: teamId,
|
|
projectId: projectId,
|
|
},
|
|
select: {
|
|
userId: true,
|
|
_id: true,
|
|
},
|
|
limit: 100,
|
|
skip: 0,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const userIds: Array<ObjectID> = teamMembers
|
|
.map((member: { userId?: ObjectID; _id?: string }) => {
|
|
return member.userId!;
|
|
})
|
|
.filter(Boolean);
|
|
|
|
// Get user details
|
|
const users: any = await UserService.findBy({
|
|
query: {
|
|
_id: new Includes(userIds),
|
|
},
|
|
select: {
|
|
name: true,
|
|
email: true,
|
|
_id: true,
|
|
profilePictureId: true,
|
|
},
|
|
limit: 100,
|
|
skip: 0,
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
// Check compliance for each user
|
|
const userComplianceStatuses: Array<UserComplianceStatus> = [];
|
|
|
|
for (const user of users) {
|
|
const complianceStatus: {
|
|
isCompliant: boolean;
|
|
nonCompliantRules: Array<{
|
|
ruleType: ComplianceRuleType;
|
|
reason: string;
|
|
}>;
|
|
} = await this.checkUserCompliance(
|
|
user.id!,
|
|
projectId,
|
|
complianceSettings.map(
|
|
(setting: { ruleType?: ComplianceRuleType; enabled?: boolean }) => {
|
|
return {
|
|
ruleType: setting.ruleType!,
|
|
enabled: setting.enabled || false,
|
|
};
|
|
},
|
|
),
|
|
);
|
|
userComplianceStatuses.push({
|
|
userId: user.id!,
|
|
userName:
|
|
user.name?.toString() || user.email?.toString() || "Unknown User",
|
|
userEmail: user.email?.toString() || "",
|
|
userProfilePictureId: user.profilePictureId,
|
|
...complianceStatus,
|
|
});
|
|
}
|
|
|
|
return {
|
|
teamId: teamId,
|
|
teamName: team.name || "Unknown Team",
|
|
complianceSettings: complianceSettings.map(
|
|
(setting: { ruleType?: ComplianceRuleType; enabled?: boolean }) => {
|
|
return {
|
|
ruleType: setting.ruleType!,
|
|
enabled: setting.enabled || false,
|
|
};
|
|
},
|
|
),
|
|
userComplianceStatuses,
|
|
};
|
|
}
|
|
|
|
private static async checkUserCompliance(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
complianceSettings: Array<{
|
|
ruleType: ComplianceRuleType;
|
|
enabled: boolean;
|
|
}>,
|
|
): Promise<{
|
|
isCompliant: boolean;
|
|
nonCompliantRules: Array<{ ruleType: ComplianceRuleType; reason: string }>;
|
|
}> {
|
|
const nonCompliantRules: Array<{
|
|
ruleType: ComplianceRuleType;
|
|
reason: string;
|
|
}> = [];
|
|
|
|
// Check each enabled compliance rule
|
|
for (const setting of complianceSettings) {
|
|
if (!setting.enabled) {
|
|
continue;
|
|
}
|
|
|
|
const isCompliant: { compliant: boolean; reason: string } =
|
|
await this.checkRuleCompliance(userId, projectId, setting.ruleType);
|
|
|
|
if (!isCompliant.compliant) {
|
|
nonCompliantRules.push({
|
|
ruleType: setting.ruleType,
|
|
reason: isCompliant.reason,
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
isCompliant: nonCompliantRules.length === 0,
|
|
nonCompliantRules,
|
|
};
|
|
}
|
|
|
|
private static async checkRuleCompliance(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
ruleType: ComplianceRuleType,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
switch (ruleType) {
|
|
case ComplianceRuleType.HasNotificationEmailMethod:
|
|
return await this.checkHasNotificationEmail(userId, projectId);
|
|
|
|
case ComplianceRuleType.HasNotificationSMSMethod:
|
|
return await this.checkHasNotificationSMS(userId, projectId);
|
|
|
|
case ComplianceRuleType.HasNotificationCallMethod:
|
|
return await this.checkHasNotificationCall(userId, projectId);
|
|
|
|
case ComplianceRuleType.HasNotificationPushMethod:
|
|
return await this.checkHasNotificationPush(userId, projectId);
|
|
|
|
case ComplianceRuleType.HasIncidentOnCallRules:
|
|
return await this.checkHasIncidentOnCallRules(userId, projectId);
|
|
|
|
case ComplianceRuleType.HasAlertOnCallRules:
|
|
return await this.checkHasAlertOnCallRules(userId, projectId);
|
|
|
|
default:
|
|
return { compliant: true, reason: "" };
|
|
}
|
|
}
|
|
|
|
private static async checkHasNotificationEmail(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
const userEmails: Array<{ _id?: string }> = await UserEmailService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
isVerified: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
const hasEmail: boolean = userEmails.length > 0;
|
|
return {
|
|
compliant: hasEmail,
|
|
reason: hasEmail
|
|
? ""
|
|
: "No verified email address configured for notifications",
|
|
};
|
|
}
|
|
|
|
private static async checkHasNotificationSMS(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
const userSMS: Array<{ _id?: string }> = await UserSmsService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
isVerified: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
const hasSMS: boolean = userSMS.length > 0;
|
|
return {
|
|
compliant: hasSMS,
|
|
reason: hasSMS
|
|
? ""
|
|
: "No verified phone number configured for SMS notifications",
|
|
};
|
|
}
|
|
|
|
private static async checkHasNotificationCall(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
const userCalls: Array<{ _id?: string }> = await UserCallService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
isVerified: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
const hasCall: boolean = userCalls.length > 0;
|
|
return {
|
|
compliant: hasCall,
|
|
reason: hasCall
|
|
? ""
|
|
: "No verified phone number configured for call notifications",
|
|
};
|
|
}
|
|
|
|
private static async checkHasNotificationPush(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
const userPush: Array<{ _id?: string }> = await UserPushService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
isVerified: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
const hasPush: boolean = userPush.length > 0;
|
|
return {
|
|
compliant: hasPush,
|
|
reason: hasPush ? "" : "No verified push notification device configured",
|
|
};
|
|
}
|
|
|
|
private static async checkHasIncidentOnCallRules(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
// Get all incident severities for the project
|
|
const incidentSeverities: Array<Partial<IncidentSeverity>> =
|
|
await IncidentSeverityService.findBy({
|
|
query: {
|
|
projectId: projectId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: LIMIT_PER_PROJECT,
|
|
skip: 0,
|
|
});
|
|
|
|
if (incidentSeverities.length === 0) {
|
|
return { compliant: true, reason: "" }; // No incident severities configured
|
|
}
|
|
|
|
// Check if user has notification rules for all incident severities
|
|
const severityIds: Array<string> = incidentSeverities.map(
|
|
(severity: Partial<IncidentSeverity>) => {
|
|
return severity._id!;
|
|
},
|
|
);
|
|
const missingSeverities: Array<string> = [];
|
|
|
|
for (const severityId of severityIds) {
|
|
const notificationRules: Array<{
|
|
_id?: string;
|
|
userCallId?: ObjectID;
|
|
userSmsId?: ObjectID;
|
|
userEmailId?: ObjectID;
|
|
userPushId?: ObjectID;
|
|
}> = await UserNotificationRuleService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
incidentSeverityId: severityId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
userCallId: true,
|
|
userSmsId: true,
|
|
userEmailId: true,
|
|
userPushId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
// Check if user has at least one notification method configured for this severity
|
|
const hasNotificationMethod: boolean = notificationRules.some(
|
|
(rule: {
|
|
_id?: string;
|
|
userCallId?: ObjectID;
|
|
userSmsId?: ObjectID;
|
|
userEmailId?: ObjectID;
|
|
userPushId?: ObjectID;
|
|
}) => {
|
|
return (
|
|
rule.userCallId ||
|
|
rule.userSmsId ||
|
|
rule.userEmailId ||
|
|
rule.userPushId
|
|
);
|
|
},
|
|
);
|
|
|
|
if (!hasNotificationMethod) {
|
|
const severity: Partial<IncidentSeverity> | undefined =
|
|
incidentSeverities.find((s: Partial<IncidentSeverity>) => {
|
|
return s._id?.toString() === severityId.toString();
|
|
});
|
|
const severityName: string = severity?.name || severityId.toString();
|
|
missingSeverities.push(severityName);
|
|
}
|
|
}
|
|
|
|
if (missingSeverities.length > 0) {
|
|
return {
|
|
compliant: false,
|
|
reason: `Missing notification rules for incident severities: ${missingSeverities.join(", ")}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
compliant: true,
|
|
reason: "",
|
|
};
|
|
}
|
|
|
|
private static async checkHasAlertOnCallRules(
|
|
userId: ObjectID,
|
|
projectId: ObjectID,
|
|
): Promise<{ compliant: boolean; reason: string }> {
|
|
// Get all alert severities for the project
|
|
const alertSeverities: Array<Partial<AlertSeverity>> =
|
|
await AlertSeverityService.findBy({
|
|
query: {
|
|
projectId: projectId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 100, // Assuming reasonable limit for severities
|
|
skip: 0,
|
|
});
|
|
|
|
if (alertSeverities.length === 0) {
|
|
return { compliant: true, reason: "" }; // No alert severities configured
|
|
}
|
|
|
|
// Check if user has notification rules for all alert severities
|
|
const severityIds: Array<string> = alertSeverities.map(
|
|
(severity: Partial<AlertSeverity>) => {
|
|
return severity._id!;
|
|
},
|
|
);
|
|
const missingSeverities: Array<string> = [];
|
|
|
|
for (const severityId of severityIds) {
|
|
const notificationRules: Array<{
|
|
_id?: string;
|
|
userCallId?: ObjectID;
|
|
userSmsId?: ObjectID;
|
|
userEmailId?: ObjectID;
|
|
userPushId?: ObjectID;
|
|
}> = await UserNotificationRuleService.findBy({
|
|
query: {
|
|
userId: userId,
|
|
projectId: projectId,
|
|
alertSeverityId: severityId,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
userCallId: true,
|
|
userSmsId: true,
|
|
userEmailId: true,
|
|
userPushId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
limit: 1,
|
|
skip: 0,
|
|
});
|
|
|
|
// Check if user has at least one notification method configured for this severity
|
|
const hasNotificationMethod: boolean = notificationRules.some(
|
|
(rule: {
|
|
_id?: string;
|
|
userCallId?: ObjectID;
|
|
userSmsId?: ObjectID;
|
|
userEmailId?: ObjectID;
|
|
userPushId?: ObjectID;
|
|
}) => {
|
|
return (
|
|
rule.userCallId ||
|
|
rule.userSmsId ||
|
|
rule.userEmailId ||
|
|
rule.userPushId
|
|
);
|
|
},
|
|
);
|
|
|
|
if (!hasNotificationMethod) {
|
|
const severity: Partial<AlertSeverity> | undefined =
|
|
alertSeverities.find((s: Partial<AlertSeverity>) => {
|
|
return s._id?.toString() === severityId.toString();
|
|
});
|
|
const severityName: string = severity?.name || severityId.toString();
|
|
missingSeverities.push(severityName);
|
|
}
|
|
}
|
|
|
|
if (missingSeverities.length > 0) {
|
|
return {
|
|
compliant: false,
|
|
reason: `Missing notification rules for alert severities: ${missingSeverities.join(", ")}`,
|
|
};
|
|
}
|
|
|
|
return {
|
|
compliant: true,
|
|
reason: "",
|
|
};
|
|
}
|
|
}
|