/home/techb158/balavpn.abdallabala.com/src/services
Edit: /home/techb158/balavpn.abdallabala.com/src/services/risk-service.js (12505B)
const { prisma } = require("../lib/prisma");
const { scoreRisk } = require("../domain/risk-engine");
const { writeAuditEvent } = require("./audit-service");
function normalizeRiskStatus(value) {
if (value === "In mitigation" || value === "IN_MITIGATION") return "IN_MITIGATION";
if (value === "Accepted" || value === "ACCEPTED") return "ACCEPTED";
if (value === "Closed" || value === "CLOSED") return "CLOSED";
return "OPEN";
}
function normalizeApprovalStatus(value) {
if (value === "Approved" || value === "APPROVED") return "APPROVED";
if (value === "Rejected" || value === "REJECTED") return "REJECTED";
if (value === "Accepted" || value === "ACCEPTED") return "ACCEPTED";
return "PENDING";
}
function normalizeMitigationStatus(value) {
if (value === "In progress" || value === "IN_PROGRESS") return "IN_PROGRESS";
if (value === "Done" || value === "DONE") return "DONE";
if (value === "Rejected" || value === "REJECTED") return "REJECTED";
return "NOT_STARTED";
}
async function listRisks(projectId) {
const risks = await prisma.risk.findMany({
where: { projectId },
include: { mitigations: true, evidence: true },
orderBy: { createdAt: "asc" }
});
return risks.map(risk => scoreRisk(Object.assign({}, risk, {
mitigationProgress: risk.mitigations.length ? Math.round(risk.mitigations.reduce((sum, item) => sum + item.progressPercent, 0) / risk.mitigations.length) : 0,
mitigationEffectiveness: risk.mitigations.length ? Math.round(risk.mitigations.reduce((sum, item) => sum + item.effectivenessPercent, 0) / risk.mitigations.length) : 0
})));
}
async function createRisk({ projectScope, actor, payload, request }) {
if (!payload.title) throw new Error("title is required");
const risk = await prisma.risk.create({
data: {
projectId: projectScope.project.id,
ownerDisplayName: payload.ownerDisplayName || payload.owner || actor.displayName,
title: payload.title,
description: payload.description || "",
dimension: payload.dimension || "Technical",
domain: payload.domain || "Technical risks",
lifecyclePhase: payload.lifecyclePhase || projectScope.project.currentLifecyclePhase || "Needs identification",
probability: Number(payload.probability || 3),
impact: Number(payload.impact || 3),
detectability: Number(payload.detectability || 3),
status: normalizeRiskStatus(payload.status),
approvalStatus: normalizeApprovalStatus(payload.approvalStatus),
dueDate: payload.dueDate ? new Date(payload.dueDate) : null,
evidenceSummary: payload.evidenceSummary || payload.evidence || ""
}
});
if (payload.evidenceTitle || payload.evidenceSummary || payload.evidenceUri) {
await prisma.evidence.create({
data: {
projectId: projectScope.project.id,
riskId: risk.id,
type: payload.evidenceType || "Review",
title: payload.evidenceTitle || payload.evidenceSummary || payload.evidenceUri,
uri: payload.evidenceUri || null,
createdById: actor.id
}
});
}
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Risk",
entityId: risk.id,
action: "create",
afterJson: risk,
request
});
return scoreRisk(risk);
}
async function updateRisk({ projectScope, riskId, actor, payload, request }) {
const before = await prisma.risk.findUnique({ where: { id: riskId } });
if (!before || before.projectId !== projectScope.project.id) {
const error = new Error(`Risk not found: ${riskId}`);
error.status = 404;
throw error;
}
const risk = await prisma.risk.update({
where: { id: riskId },
data: {
title: payload.title ?? before.title,
description: payload.description ?? before.description,
dimension: payload.dimension ?? before.dimension,
domain: payload.domain ?? before.domain,
lifecyclePhase: payload.lifecyclePhase ?? before.lifecyclePhase,
probability: payload.probability !== undefined ? Number(payload.probability) : before.probability,
impact: payload.impact !== undefined ? Number(payload.impact) : before.impact,
detectability: payload.detectability !== undefined ? Number(payload.detectability) : before.detectability,
status: payload.status !== undefined ? normalizeRiskStatus(payload.status) : before.status,
approvalStatus: payload.approvalStatus !== undefined ? normalizeApprovalStatus(payload.approvalStatus) : before.approvalStatus,
dueDate: payload.dueDate !== undefined ? payload.dueDate ? new Date(payload.dueDate) : null : before.dueDate,
evidenceSummary: payload.evidenceSummary ?? before.evidenceSummary
}
});
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Risk",
entityId: risk.id,
action: "update",
beforeJson: before,
afterJson: risk,
request
});
return scoreRisk(risk);
}
async function deleteRisk({ projectScope, riskId, actor, request }) {
const before = await prisma.risk.findUnique({ where: { id: riskId } });
if (!before || before.projectId !== projectScope.project.id) {
const error = new Error(`Risk not found: ${riskId}`);
error.status = 404;
throw error;
}
await prisma.risk.delete({ where: { id: riskId } });
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Risk",
entityId: riskId,
action: "delete",
beforeJson: before,
request
});
return { deleted: true };
}
async function createMitigation({ projectScope, riskId, actor, payload, request }) {
const risk = await prisma.risk.findUnique({ where: { id: riskId } });
if (!risk || risk.projectId !== projectScope.project.id) {
const error = new Error(`Risk not found: ${riskId}`);
error.status = 404;
throw error;
}
const mitigation = await prisma.mitigation.create({
data: {
riskId,
ownerDisplayName: payload.ownerDisplayName || payload.owner || actor.displayName,
title: payload.title,
description: payload.description || "",
status: normalizeMitigationStatus(payload.status),
progressPercent: Number(payload.progressPercent ?? payload.progress_percent ?? 0),
effectivenessPercent: Number(payload.effectivenessPercent ?? payload.effectiveness_percent ?? 0),
dueDate: payload.dueDate ? new Date(payload.dueDate) : null,
sequence: Number(payload.sequence || 1)
}
});
if (payload.evidenceTitle || payload.evidenceUri) {
await prisma.evidence.create({
data: {
projectId: projectScope.project.id,
riskId,
mitigationId: mitigation.id,
type: payload.evidenceType || "Review",
title: payload.evidenceTitle || payload.evidenceUri,
uri: payload.evidenceUri || null,
createdById: actor.id
}
});
}
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Mitigation",
entityId: mitigation.id,
action: "create",
afterJson: mitigation,
request
});
return mitigation;
}
async function getMitigation(mitigationId) {
const mitigation = await prisma.mitigation.findUnique({
where: { id: mitigationId },
include: { evidence: true, risk: { select: { id: true, title: true, dimension: true, projectId: true } } }
});
if (!mitigation) {
const error = new Error(`Mitigation not found: ${mitigationId}`);
error.status = 404;
throw error;
}
return mitigation;
}
async function listProjectMitigations(projectId) {
return prisma.mitigation.findMany({
where: { risk: { projectId } },
include: { evidence: true, risk: { select: { id: true, title: true, dimension: true } } },
orderBy: { createdAt: "desc" }
});
}
async function updateMitigation({ projectScope, mitigationId, actor, payload, request }) {
const before = await prisma.mitigation.findUnique({
where: { id: mitigationId },
include: { risk: { select: { projectId: true } } }
});
if (!before || before.risk.projectId !== projectScope.project.id) {
const error = new Error(`Mitigation not found: ${mitigationId}`);
error.status = 404;
throw error;
}
const mitigation = await prisma.mitigation.update({
where: { id: mitigationId },
data: {
title: payload.title ?? before.title,
description: payload.description ?? before.description,
ownerDisplayName: payload.ownerDisplayName ?? payload.owner ?? before.ownerDisplayName,
status: payload.status !== undefined ? normalizeMitigationStatus(payload.status) : before.status,
progressPercent: payload.progressPercent !== undefined ? Number(payload.progressPercent) : before.progressPercent,
effectivenessPercent: payload.effectivenessPercent !== undefined ? Number(payload.effectivenessPercent) : before.effectivenessPercent,
dueDate: payload.dueDate !== undefined ? payload.dueDate ? new Date(payload.dueDate) : null : before.dueDate,
sequence: payload.sequence !== undefined ? Number(payload.sequence) : before.sequence
}
});
if (payload.evidenceTitle || payload.evidenceUri) {
await prisma.evidence.create({
data: {
projectId: projectScope.project.id,
riskId: before.riskId,
mitigationId: mitigation.id,
type: payload.evidenceType || "Review",
title: payload.evidenceTitle || payload.evidenceUri,
uri: payload.evidenceUri || null,
createdById: actor.id
}
});
}
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Mitigation",
entityId: mitigation.id,
action: "update",
beforeJson: before,
afterJson: mitigation,
request
});
return mitigation;
}
async function deleteMitigation({ projectScope, mitigationId, actor, request }) {
const before = await prisma.mitigation.findUnique({
where: { id: mitigationId },
include: { risk: { select: { projectId: true } } }
});
if (!before || before.risk.projectId !== projectScope.project.id) {
const error = new Error(`Mitigation not found: ${mitigationId}`);
error.status = 404;
throw error;
}
await prisma.mitigation.delete({ where: { id: mitigationId } });
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Mitigation",
entityId: mitigationId,
action: "delete",
beforeJson: before,
request
});
return { deleted: true };
}
async function addEvidenceToMitigation({ projectScope, mitigationId, actor, payload, request }) {
const mitigation = await prisma.mitigation.findUnique({
where: { id: mitigationId },
include: { risk: { select: { projectId: true } } }
});
if (!mitigation || mitigation.risk.projectId !== projectScope.project.id) {
const error = new Error(`Mitigation not found: ${mitigationId}`);
error.status = 404;
throw error;
}
const evidence = await prisma.evidence.create({
data: {
projectId: projectScope.project.id,
riskId: mitigation.riskId,
mitigationId: mitigation.id,
type: payload.type || "Review",
title: payload.title,
uri: payload.uri || null,
createdById: actor.id
}
});
await writeAuditEvent({
organizationId: projectScope.organization.id,
workspaceId: projectScope.project.workspaceId,
projectId: projectScope.project.id,
actorUserId: actor.id,
entityType: "Evidence",
entityId: evidence.id,
action: "create",
afterJson: evidence,
request
});
return evidence;
}
module.exports = {
listRisks,
createRisk,
updateRisk,
deleteRisk,
createMitigation,
getMitigation,
listProjectMitigations,
updateMitigation,
deleteMitigation,
addEvidenceToMitigation,
normalizeRiskStatus,
normalizeApprovalStatus
};