feat(katalog): Profil-Editor mit Live-Prüfung und Skriptvorschau

Admins legen eigene Profile als YAML an; geprüft wird beim Tippen gegen
dasselbe Schema wie der Katalog, dazu eine Vorschau des erzeugten
Skripts. Katalogprofile bleiben schreibgeschützt und lassen sich als
Kopie bearbeiten. Ein Profil, das ein anderes voraussetzt, lässt sich
nicht löschen.

Closes #011

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:44:28 +02:00
co-authored by Claude Opus 5.5
parent d25ec54ef9
commit e4306a6a53
11 changed files with 529 additions and 14 deletions
+17 -1
View File
@@ -3,7 +3,7 @@ import { aufruferAdresse } from "@/lib/adressen";
import { z } from "@/lib/validierung";
import { api, jsonLesen } from "@/server/api";
import { rechtErforderlich } from "@/server/auth/zugang";
import { profilAktivSetzen } from "@/server/services/katalog";
import { profilAktivSetzen, profilLoeschen, profilSpeichern } from "@/server/services/katalog";
type Kontext = { params: Promise<{ id: string }> };
@@ -13,3 +13,19 @@ export const PATCH = api(async (anfrage: Request, { params }: Kontext) => {
await profilAktivSetzen((await params).id, aktiv, aufrufer, aufruferAdresse(anfrage.headers));
return NextResponse.json({ ok: true });
});
export const PUT = api(async (anfrage: Request, { params }: Kontext) => {
const aufrufer = await rechtErforderlich(anfrage, "katalogVerwalten");
const { quelltext } = z
.object({ quelltext: z.string().min(1).max(200_000) })
.parse(await jsonLesen(anfrage));
return NextResponse.json(
await profilSpeichern(quelltext, aufrufer, aufruferAdresse(anfrage.headers), (await params).id),
);
});
export const DELETE = api(async (anfrage: Request, { params }: Kontext) => {
const aufrufer = await rechtErforderlich(anfrage, "katalogVerwalten");
await profilLoeschen((await params).id, aufrufer, aufruferAdresse(anfrage.headers));
return new NextResponse(null, { status: 204 });
});