feat: add emergency incident workspace
This commit is contained in:
152
modules/emergency/server/operations.ts
Normal file
152
modules/emergency/server/operations.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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<string, string> {
|
||||
return new Map(rows.map((row) => [row.id, row.name]));
|
||||
}
|
||||
|
||||
function toEmergencyIncident(row: typeof systemIncidents.$inferSelect, accountNameById: Map<string, string>): 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<EmergencyIncidentData> {
|
||||
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<EmergencyIncident | EmergencyMutationFailure> {
|
||||
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<EmergencyIncident | EmergencyMutationFailure> {
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user