import { alsAnsicht, type BenachrichtigungAnsicht } from "@/lib/benachrichtigung/einstellungen"; import { cronBeschreiben, webhookZielKurz } from "@/lib/benachrichtigung/regeln"; import { prisma } from "@/lib/db"; import { VERSION } from "@/lib/version"; import { oidcAktiv, oidcName } from "@/server/auth/oidc"; import { benachrichtigungLesen } from "@/server/services/benachrichtigung"; /** * Lesemodell der Einstellungsseite (#028). Lesen duerfen alle Rollen; Geheimnisse verlassen * diese Datei nur als „gesetzt ja/nein“ (`alsAnsicht`). */ export type ZustellungZeile = { readonly sendung: string; readonly zeitpunkt: Date; readonly kanal: "EMAIL" | "WEBHOOK"; readonly betreff: string; readonly anlaesse: number; readonly ergebnis: "GESENDET" | "FEHLGESCHLAGEN"; readonly versuche: number; readonly fehler: string | null; readonly naechsterVersuchAm: Date | null; }; export type Zeitplan = { readonly ausdruck: string; readonly beschreibung: string | null }; export type EinstellungenSeite = { readonly benachrichtigung: BenachrichtigungAnsicht; readonly webhookZiel: string | null; readonly zustellungen: readonly ZustellungZeile[]; readonly abgleich: Zeitplan; readonly meldung: Zeitplan; readonly instanz: { readonly version: string; readonly deploymentKey: { readonly name: string; readonly fingerprint: string } | null; readonly oidc: { readonly name: string; readonly konfiguriert: boolean }; readonly notfall: { readonly vorhanden: boolean; readonly totpEingerichtet: boolean }; }; }; function zeitplan(variable: string, standard: string): Zeitplan { const ausdruck = process.env[variable]?.trim() || standard; return { ausdruck, beschreibung: cronBeschreiben(ausdruck) }; } /** Je Nachricht eine Zeile: alle Anlaesse einer Sendung teilen Kennung, Betreff und Ergebnis. */ export async function letzteZustellungen(anzahl = 12): Promise { const zeilen = await prisma.zustellung.findMany({ orderBy: { zeitpunkt: "desc" }, take: 500, select: { sendung: true, zeitpunkt: true, kanal: true, betreff: true, ergebnis: true, versuche: true, fehler: true, naechsterVersuchAm: true, }, }); const gruppen = new Map(); for (const z of zeilen) { const vorhanden = gruppen.get(z.sendung); if (vorhanden) { gruppen.set(z.sendung, { ...vorhanden, anlaesse: vorhanden.anlaesse + 1, versuche: Math.max(vorhanden.versuche, z.versuche), }); } else { if (gruppen.size >= anzahl) continue; gruppen.set(z.sendung, { ...z, anlaesse: 1 }); } } return [...gruppen.values()]; } export async function einstellungenSeite(): Promise { const [gespeichert, zustellungen, deploymentKey, notfall] = await Promise.all([ benachrichtigungLesen(), letzteZustellungen(), prisma.schluessel.findFirst({ where: { instanzStandard: true }, select: { name: true, fingerprint: true }, }), prisma.benutzer.findFirst({ where: { istNotfall: true }, select: { totpEingerichtetAm: true } }), ]); return { benachrichtigung: alsAnsicht(gespeichert), webhookZiel: gespeichert.webhook.url ? webhookZielKurz(gespeichert.webhook.url) : null, zustellungen, abgleich: zeitplan("SSHKV_SYNC_CRON", "30 2 * * *"), meldung: zeitplan("SSHKV_MELDUNG_CRON", "0 7 * * *"), instanz: { version: VERSION, deploymentKey, oidc: { name: oidcName(), konfiguriert: oidcAktiv() }, notfall: { vorhanden: notfall !== null, totpEingerichtet: Boolean(notfall?.totpEingerichtetAm) }, }, }; }