import { and, desc, eq, inArray } from "drizzle-orm"; import { getDatabase } from "@/modules/core/server/db"; import { accounts, systemIncidents } from "@/modules/core/server/schema"; import type { EmergencyIncident, EmergencyIncidentCreateInput, EmergencyIncidentData, EmergencyIncidentStatusUpdateInput, } from "@/modules/emergency/types"; export type EmergencyMutationFailure = { success: false; reason: string; status: number; }; type AccountNameRow = { id: string; name: string; }; export function isEmergencyMutationFailure(value: unknown): value is EmergencyMutationFailure { return ( typeof value === "object" && value !== null && "success" in value && (value as { success?: unknown }).success === false ); } function iso(value: Date): string { return value.toISOString(); } function optionalIso(value: Date | null): string | undefined { return value ? iso(value) : undefined; } function buildNameMap(rows: AccountNameRow[]): Map { return new Map(rows.map((row) => [row.id, row.name])); } function toEmergencyIncident(row: typeof systemIncidents.$inferSelect, accountNameById: Map): EmergencyIncident { const acknowledgedByAccountId = row.acknowledgedByAccountId ?? undefined; const resolvedByAccountId = row.resolvedByAccountId ?? undefined; return { id: row.id, organizationId: row.organizationId ?? undefined, severity: row.severity, status: row.status, title: row.title, description: row.description, source: row.source, acknowledgedByAccountId, acknowledgedByName: acknowledgedByAccountId ? accountNameById.get(acknowledgedByAccountId) : undefined, acknowledgedAt: optionalIso(row.acknowledgedAt), resolvedByAccountId, resolvedByName: resolvedByAccountId ? accountNameById.get(resolvedByAccountId) : undefined, resolvedAt: optionalIso(row.resolvedAt), createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } export async function listEmergencyIncidentData(organizationId: string): Promise { const database = getDatabase(); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); const incidentRows = await database .select() .from(systemIncidents) .where(eq(systemIncidents.organizationId, organizationId)) .orderBy(desc(systemIncidents.createdAt)); const accountIds = Array.from( new Set( incidentRows .flatMap((incident) => [incident.acknowledgedByAccountId, incident.resolvedByAccountId]) .filter((accountId): accountId is string => typeof accountId === "string"), ), ); const accountRows = accountIds.length > 0 ? await database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(inArray(accounts.id, accountIds)) : []; const accountNameById = buildNameMap(accountRows); return { incidents: incidentRows.map((incident) => toEmergencyIncident(incident, accountNameById)), metrics: { open: incidentRows.filter((incident) => incident.status === "open").length, acknowledged: incidentRows.filter((incident) => incident.status === "acknowledged").length, critical: incidentRows.filter((incident) => incident.severity === "critical").length, resolvedToday: incidentRows.filter( (incident) => (incident.status === "resolved" || incident.status === "closed") && incident.resolvedAt !== null && incident.resolvedAt >= todayStart, ).length, }, }; } export async function createEmergencyIncident( input: EmergencyIncidentCreateInput & { organizationId: string }, ): Promise { const database = getDatabase(); const rows = await database .insert(systemIncidents) .values({ organizationId: input.organizationId, severity: input.severity, status: "open", title: input.title, description: input.description, source: input.source, }) .returning(); const incident = rows[0]; if (!incident) { return { success: false, reason: "应急事件创建失败", status: 500 }; } return toEmergencyIncident(incident, new Map()); } export async function updateEmergencyIncidentStatus( input: EmergencyIncidentStatusUpdateInput & { accountId: string; id: string; organizationId: string }, ): Promise { const database = getDatabase(); const now = new Date(); const rows = await database .update(systemIncidents) .set({ status: input.status, updatedAt: now, acknowledgedByAccountId: input.status === "acknowledged" ? input.accountId : undefined, acknowledgedAt: input.status === "acknowledged" ? now : undefined, resolvedByAccountId: input.status === "resolved" || input.status === "closed" ? input.accountId : undefined, resolvedAt: input.status === "resolved" || input.status === "closed" ? now : undefined, }) .where(and(eq(systemIncidents.id, input.id), eq(systemIncidents.organizationId, input.organizationId))) .returning(); const incident = rows[0]; if (!incident) { return { success: false, reason: "应急事件不存在", status: 404 }; } const accountRows = await database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(eq(accounts.id, input.accountId)); return toEmergencyIncident(incident, buildNameMap(accountRows)); }