feat(ops): health-fail alerts, optional DB backup, admin UX polish

Wire jobs-worker health probes to Discord/email alerts with cooldown, optional mysqldump, rate-limit /api/health, mark-all-read alerts, ConfirmDialog on destructive admin actions, and raise coverage floors.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 15:25:47 +02:00
1 parent 3bd712e744
commit 725e1cb338
16 files changed
+327 -32

No files matched your search

+27
View File
@@ -232,6 +232,33 @@ export function emulatorOffline(detail?: string): Promise<SendAlertResult> {
});
}
/**
* Raise an ERROR alert when ops health is degraded (DB / Redis / emulator).
*/
export function healthDegraded(parts: {
database: boolean;
redis: boolean | null;
emulator: boolean;
}): Promise<SendAlertResult> {
const failed: string[] = [];
if (!parts.database) failed.push("database");
if (parts.redis === false) failed.push("redis");
if (!parts.emulator) failed.push("emulator");
return sendAlert({
type: "health",
severity: parts.database ? "error" : "critical",
message:
failed.length > 0
? `Ops health degraded: ${failed.join(", ")} unavailable.`
: "Ops health degraded.",
context: {
database: parts.database,
redis: parts.redis,
emulator: parts.emulator,
},
});
}
/**
* Raise a WARNING alert that a possible DDoS / abusive request pattern was
* detected from a single IP (count = requests seen in the sampling window).
+17
View File
@@ -179,6 +179,23 @@ describe("staff smoke contract", () => {
);
});
it("ships ops health alerts, optional DB backup, and health rate limit", () => {
const worker = readFileSync("scripts/jobs-worker.ts", "utf8");
expect(worker).toContain("checkOpsHealth");
expect(worker).toContain("healthDegraded");
expect(worker).toContain("backupDatabase");
expect(readFileSync("src/lib/services/alert.ts", "utf8")).toContain(
"healthDegraded",
);
expect(readFileSync("src/app/api/health/route.ts", "utf8")).toContain(
"rateLimit",
);
expect(readFileSync("src/actions/admin-alerts.ts", "utf8")).toContain(
"markAllAlertsRead",
);
expect(readFileSync("src/env.ts", "utf8")).toContain("DB_BACKUP_DIR");
});
it("documents local-only photo file purge", () => {
expect(readFileSync("src/app/admin/photos/page.tsx", "utf8")).toContain(
"purgeHint",