feat(housekeeping): deliver system vertical

This commit is contained in:
Simo committed 2026-08-28 23:31:47 +02:00
1 parent 0117b45d74
commit 3788ecd9f1
33 files changed
+4548 -423

No files matched your search

+34 -10
View File
@@ -1,11 +1,30 @@
"use server";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import {
type SystemMutationContext,
systemMutationService,
} from "@/features/housekeeping/domains/system/services/mutations";
import { createHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/capability-context";
import { createCorrelationId } from "@/features/housekeeping/foundation/correlation";
import { requirePermission } from "@/lib/admin/guard";
import { AlertLogs, db } from "@/lib/db";
import { PERMS } from "@/lib/permissions";
import { rcon } from "@/lib/services/rcon";
function grantedMutationContext(
staff: { id: number; rank: number; username: string },
permission: string,
): SystemMutationContext {
const matches = (slug: string) => slug === permission;
return {
capability: createHousekeepingCapabilityContext(staff, {
isSuperAdmin: false,
has: matches,
hasAny: (...slugs) => slugs.some(matches),
hasAll: (...slugs) => slugs.every(matches),
}),
correlationId: createCorrelationId(),
};
}
/**
* Broadcast a hotel-wide alert to every online user via RCON.
@@ -14,7 +33,7 @@ import { rcon } from "@/lib/services/rcon";
* `message` payload. Staff-gated; the message is trimmed/bounded before send.
*/
export async function sendHotelAlert(formData: FormData): Promise<void> {
await requirePermission(PERMS.NOTIFICATIONS_EDIT);
const staff = await requirePermission(PERMS.NOTIFICATIONS_EDIT);
const message = String(formData.get("message") ?? "")
.normalize("NFC")
@@ -23,7 +42,11 @@ export async function sendHotelAlert(formData: FormData): Promise<void> {
if (!message) return;
try {
await rcon.send("hotelalert", { message });
await systemMutationService.execute(
grantedMutationContext(staff, PERMS.NOTIFICATIONS_EDIT),
"operations.alert.broadcast",
{ message },
);
} catch {
// Best-effort delivery (dead socket / emulator offline) — never 500 the
// admin page. The emulator writes its own alert_logs row on receipt.
@@ -34,12 +57,13 @@ export async function sendHotelAlert(formData: FormData): Promise<void> {
/** Mark every unread ops alert as read. */
export async function markAllAlertsRead(): Promise<void> {
await requirePermission(PERMS.NOTIFICATIONS_VIEW);
const staff = await requirePermission(PERMS.NOTIFICATIONS_VIEW);
try {
await db
.update(AlertLogs)
.set({ isRead: true, updatedAt: new Date() })
.where(eq(AlertLogs.isRead, false));
await systemMutationService.execute(
grantedMutationContext(staff, PERMS.NOTIFICATIONS_VIEW),
"operations.alerts.mark-read",
{},
);
} catch {
/* ignore */
}