feat: build collaboration workspaces
This commit is contained in:
242
modules/alerts/server/operations.ts
Normal file
242
modules/alerts/server/operations.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import { and, desc, eq, inArray } from "drizzle-orm";
|
||||
|
||||
import { getDatabase } from "@/modules/core/server/db";
|
||||
import { accounts, alertRules, alertTriggers, elders } from "@/modules/core/server/schema";
|
||||
import type { AlertCenterData, AlertRule, AlertRuleInput, AlertTrigger, AlertTriggerInput } from "@/modules/alerts/types";
|
||||
|
||||
export type AlertMutationFailure = {
|
||||
success: false;
|
||||
reason: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
export function isAlertMutationFailure(value: unknown): value is AlertMutationFailure {
|
||||
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 toAlertRule(row: typeof alertRules.$inferSelect): AlertRule {
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
name: row.name,
|
||||
ruleType: row.ruleType,
|
||||
severity: row.severity,
|
||||
status: row.status,
|
||||
conditionSummary: row.conditionSummary,
|
||||
suggestion: row.suggestion,
|
||||
createdAt: iso(row.createdAt),
|
||||
updatedAt: iso(row.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
function toAlertTrigger(
|
||||
row: typeof alertTriggers.$inferSelect,
|
||||
ruleNameById: Map<string, string>,
|
||||
elderNameById: Map<string, string>,
|
||||
accountNameById: Map<string, string>,
|
||||
): AlertTrigger {
|
||||
const ruleId = row.ruleId ?? undefined;
|
||||
const elderId = row.elderId ?? undefined;
|
||||
const handledByAccountId = row.handledByAccountId ?? undefined;
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
ruleId,
|
||||
ruleName: ruleId ? ruleNameById.get(ruleId) ?? "" : "",
|
||||
elderId,
|
||||
elderName: elderId ? elderNameById.get(elderId) ?? "" : "",
|
||||
title: row.title,
|
||||
description: row.description,
|
||||
status: row.status,
|
||||
source: row.source,
|
||||
handledByAccountId,
|
||||
handledByName: handledByAccountId ? accountNameById.get(handledByAccountId) : undefined,
|
||||
handledAt: optionalIso(row.handledAt),
|
||||
handlingNotes: row.handlingNotes,
|
||||
createdAt: iso(row.createdAt),
|
||||
updatedAt: iso(row.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
async function validateRuleAndElder(input: {
|
||||
elderId?: string;
|
||||
organizationId: string;
|
||||
ruleId?: string;
|
||||
}): Promise<{
|
||||
elderNameById: Map<string, string>;
|
||||
ruleNameById: Map<string, string>;
|
||||
} | AlertMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const [ruleRows, elderRows] = await Promise.all([
|
||||
input.ruleId
|
||||
? database.select({ id: alertRules.id, name: alertRules.name }).from(alertRules).where(and(eq(alertRules.id, input.ruleId), eq(alertRules.organizationId, input.organizationId)))
|
||||
: [],
|
||||
input.elderId
|
||||
? database.select({ id: elders.id, name: elders.name }).from(elders).where(and(eq(elders.id, input.elderId), eq(elders.organizationId, input.organizationId)))
|
||||
: [],
|
||||
]);
|
||||
if (input.ruleId && !ruleRows[0]) {
|
||||
return { success: false, reason: "预警规则不存在", status: 404 };
|
||||
}
|
||||
if (input.elderId && !elderRows[0]) {
|
||||
return { success: false, reason: "老人档案不存在", status: 404 };
|
||||
}
|
||||
|
||||
return {
|
||||
ruleNameById: new Map(ruleRows.map((rule) => [rule.id, rule.name])),
|
||||
elderNameById: new Map(elderRows.map((elder) => [elder.id, elder.name])),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listAlertCenterData(organizationId: string): Promise<AlertCenterData> {
|
||||
const database = getDatabase();
|
||||
const [ruleRows, triggerRows] = await Promise.all([
|
||||
database.select().from(alertRules).where(eq(alertRules.organizationId, organizationId)).orderBy(desc(alertRules.updatedAt)),
|
||||
database.select().from(alertTriggers).where(eq(alertTriggers.organizationId, organizationId)).orderBy(desc(alertTriggers.updatedAt)),
|
||||
]);
|
||||
const ruleIds = Array.from(new Set(triggerRows.map((trigger) => trigger.ruleId).filter((id): id is string => typeof id === "string")));
|
||||
const elderIds = Array.from(new Set(triggerRows.map((trigger) => trigger.elderId).filter((id): id is string => typeof id === "string")));
|
||||
const accountIds = Array.from(new Set(triggerRows.map((trigger) => trigger.handledByAccountId).filter((id): id is string => typeof id === "string")));
|
||||
const [triggerRuleRows, elderRows, accountRows] = await Promise.all([
|
||||
ruleIds.length > 0 ? database.select({ id: alertRules.id, name: alertRules.name }).from(alertRules).where(inArray(alertRules.id, ruleIds)) : [],
|
||||
elderIds.length > 0 ? database.select({ id: elders.id, name: elders.name }).from(elders).where(and(eq(elders.organizationId, organizationId), inArray(elders.id, elderIds))) : [],
|
||||
accountIds.length > 0 ? database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(inArray(accounts.id, accountIds)) : [],
|
||||
]);
|
||||
const ruleNameById = new Map([...ruleRows.map((rule) => [rule.id, rule.name] as const), ...triggerRuleRows.map((rule) => [rule.id, rule.name] as const)]);
|
||||
|
||||
return {
|
||||
metrics: {
|
||||
enabledRules: ruleRows.filter((rule) => rule.status === "enabled").length,
|
||||
openTriggers: triggerRows.filter((trigger) => trigger.status === "open" || trigger.status === "acknowledged").length,
|
||||
criticalTriggers: triggerRows.filter((trigger) => {
|
||||
const ruleId = trigger.ruleId;
|
||||
const rule = ruleId ? ruleRows.find((item) => item.id === ruleId) : undefined;
|
||||
return rule?.severity === "critical";
|
||||
}).length,
|
||||
resolvedTriggers: triggerRows.filter((trigger) => trigger.status === "resolved" || trigger.status === "closed").length,
|
||||
},
|
||||
rules: ruleRows.map(toAlertRule),
|
||||
triggers: triggerRows.map((trigger) =>
|
||||
toAlertTrigger(
|
||||
trigger,
|
||||
ruleNameById,
|
||||
new Map(elderRows.map((elder) => [elder.id, elder.name])),
|
||||
new Map(accountRows.map((account) => [account.id, account.name])),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createAlertRule(input: AlertRuleInput & { organizationId: string }): Promise<AlertRule | AlertMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const rows = await database.insert(alertRules).values({ ...input }).returning();
|
||||
const rule = rows[0];
|
||||
return rule ? toAlertRule(rule) : { success: false, reason: "预警规则创建失败", status: 500 };
|
||||
}
|
||||
|
||||
export async function updateAlertRule(input: AlertRuleInput & { id: string; organizationId: string }): Promise<AlertRule | AlertMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const rows = await database
|
||||
.update(alertRules)
|
||||
.set({
|
||||
name: input.name,
|
||||
ruleType: input.ruleType,
|
||||
severity: input.severity,
|
||||
status: input.status,
|
||||
conditionSummary: input.conditionSummary,
|
||||
suggestion: input.suggestion,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(alertRules.id, input.id), eq(alertRules.organizationId, input.organizationId)))
|
||||
.returning();
|
||||
const rule = rows[0];
|
||||
return rule ? toAlertRule(rule) : { success: false, reason: "预警规则不存在", status: 404 };
|
||||
}
|
||||
|
||||
export async function deleteAlertRule(input: { id: string; organizationId: string }): Promise<AlertRule | AlertMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const rows = await database.delete(alertRules).where(and(eq(alertRules.id, input.id), eq(alertRules.organizationId, input.organizationId))).returning();
|
||||
const rule = rows[0];
|
||||
return rule ? toAlertRule(rule) : { success: false, reason: "预警规则不存在", status: 404 };
|
||||
}
|
||||
|
||||
export async function createAlertTrigger(input: AlertTriggerInput & { accountId: string; organizationId: string }): Promise<AlertTrigger | AlertMutationFailure> {
|
||||
const maps = await validateRuleAndElder(input);
|
||||
if (isAlertMutationFailure(maps)) {
|
||||
return maps;
|
||||
}
|
||||
|
||||
const database = getDatabase();
|
||||
const now = new Date();
|
||||
const handledAt = input.status === "resolved" || input.status === "closed" || input.status === "acknowledged" ? now : undefined;
|
||||
const rows = await database
|
||||
.insert(alertTriggers)
|
||||
.values({
|
||||
organizationId: input.organizationId,
|
||||
ruleId: input.ruleId,
|
||||
elderId: input.elderId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
status: input.status,
|
||||
source: input.source,
|
||||
handledByAccountId: handledAt ? input.accountId : undefined,
|
||||
handledAt,
|
||||
handlingNotes: input.handlingNotes,
|
||||
})
|
||||
.returning();
|
||||
const trigger = rows[0];
|
||||
return trigger
|
||||
? toAlertTrigger(trigger, maps.ruleNameById, maps.elderNameById, new Map())
|
||||
: { success: false, reason: "预警触发记录创建失败", status: 500 };
|
||||
}
|
||||
|
||||
export async function updateAlertTrigger(input: AlertTriggerInput & { accountId: string; id: string; organizationId: string }): Promise<AlertTrigger | AlertMutationFailure> {
|
||||
const maps = await validateRuleAndElder(input);
|
||||
if (isAlertMutationFailure(maps)) {
|
||||
return maps;
|
||||
}
|
||||
|
||||
const database = getDatabase();
|
||||
const now = new Date();
|
||||
const handledAt = input.status === "resolved" || input.status === "closed" || input.status === "acknowledged" ? now : null;
|
||||
const rows = await database
|
||||
.update(alertTriggers)
|
||||
.set({
|
||||
ruleId: input.ruleId ?? null,
|
||||
elderId: input.elderId ?? null,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
status: input.status,
|
||||
source: input.source,
|
||||
handledByAccountId: handledAt ? input.accountId : null,
|
||||
handledAt,
|
||||
handlingNotes: input.handlingNotes,
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(and(eq(alertTriggers.id, input.id), eq(alertTriggers.organizationId, input.organizationId)))
|
||||
.returning();
|
||||
const trigger = rows[0];
|
||||
return trigger
|
||||
? toAlertTrigger(trigger, maps.ruleNameById, maps.elderNameById, new Map([[input.accountId, ""]]))
|
||||
: { success: false, reason: "预警触发记录不存在", status: 404 };
|
||||
}
|
||||
|
||||
export async function deleteAlertTrigger(input: { id: string; organizationId: string }): Promise<AlertTrigger | AlertMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const rows = await database.delete(alertTriggers).where(and(eq(alertTriggers.id, input.id), eq(alertTriggers.organizationId, input.organizationId))).returning();
|
||||
const trigger = rows[0];
|
||||
return trigger ? toAlertTrigger(trigger, new Map(), new Map(), new Map()) : { success: false, reason: "预警触发记录不存在", status: 404 };
|
||||
}
|
||||
Reference in New Issue
Block a user