Files
nebula/src/app/api/admin/settings/route.ts
T
Florian WildeandClaude Opus 5 e273a0dcaa feat(admin): Kontoverwaltung und Admin-Einstellungen
"Mein Konto": Profil, Passwort setzen oder wechseln, Passkeys anlegen,
umbenennen und entfernen, aktive Sitzungen einsehen und ueberall abmelden.
Der letzte Passkey laesst sich nicht entfernen, solange kein Passwort
gesetzt ist - sonst waere das Konto ausgesperrt.

Admin-Einstellungen: Betriebsart umschalten, Selbstregistrierung freigeben,
Konten anlegen, bearbeiten, deaktivieren und loeschen. Die Instanzwerte aus
der .env werden angezeigt, damit sichtbar ist, warum Passkeys gegebenenfalls
nicht verfuegbar sind.

Schutzregeln:
- Der Wechsel in den Einzelbetrieb wird abgelehnt, solange mehr als ein
  Konto existiert.
- Der letzte aktive Administrator kann weder herabgestuft noch deaktiviert
  oder geloescht werden.
- Das eigene Konto laesst sich nicht loeschen oder deaktivieren.
- Deaktivieren oder ein neues Passwort beenden sofort alle Sitzungen des
  Kontos.

Administratoren verwalten Konten, keine Inhalte: fremde Projekte sehen auch
sie nicht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 21:30:33 +02:00

65 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextResponse } from "next/server";
import { requireAdminApi } from "@/lib/auth/guard";
import { getAppSettings } from "@/lib/auth/settings";
import { prisma } from "@/lib/db";
import { jsonError, readJsonBody } from "@/lib/security/request";
import { adminSettingsSchema, formatZodError } from "@/lib/validation";
export const dynamic = "force-dynamic";
export async function GET() {
const auth = await requireAdminApi();
if (!auth.ok) return auth.response;
const [settings, userCount] = await Promise.all([getAppSettings(), prisma.user.count()]);
return NextResponse.json({ settings, userCount });
}
export async function PATCH(request: Request) {
const auth = await requireAdminApi();
if (!auth.ok) return auth.response;
const body = await readJsonBody(request);
if (!body.ok) return body.response;
const parsed = adminSettingsSchema.safeParse(body.data);
if (!parsed.success) {
return NextResponse.json(
{ error: "Validierung fehlgeschlagen", fields: formatZodError(parsed.error) },
{ status: 422 },
);
}
const { mode, allowRegistration } = parsed.data;
// Der Wechsel in den Einzelbetrieb wuerde weitere Konten aussperren –
// deshalb nur erlauben, wenn es wirklich nur ein Konto gibt.
if (mode === "SINGLE_USER") {
const userCount = await prisma.user.count();
if (userCount > 1) {
return jsonError(
`Einzelbetrieb ist nicht moeglich, solange ${userCount} Konten existieren. ` +
"Bitte zuerst die uebrigen Konten entfernen.",
409,
);
}
}
const settings = await prisma.appSettings.upsert({
where: { id: "singleton" },
create: {
id: "singleton",
...(mode ? { mode } : {}),
...(allowRegistration !== undefined ? { allowRegistration } : {}),
},
update: {
...(mode ? { mode } : {}),
...(allowRegistration !== undefined ? { allowRegistration } : {}),
// Im Einzelbetrieb ergibt eine offene Registrierung keinen Sinn.
...(mode === "SINGLE_USER" ? { allowRegistration: false } : {}),
},
});
return NextResponse.json({ settings });
}