import { NextResponse } from "next/server"; import { createLanguage, listLanguages, listTranslations, } from "@/lib/db/languages"; import { computeCompleteness, currentTemplateHash } from "@/lib/i18n/template"; import { ForbiddenError, requireRole, UnauthorizedError } from "@/lib/auth/session"; import { isSameOrigin } from "@/lib/origin"; // Sprachverwaltung (nur Admin) – gleiches Auth-/CSRF-Muster wie // app/api/admin/users/route.ts. async function requireAdmin(request: Request) { if (!isSameOrigin(request)) throw new ForbiddenError(); return requireRole(["admin"]); } function errorResponse(err: unknown) { if (err instanceof UnauthorizedError || err instanceof ForbiddenError) { return NextResponse.json({ error: "Nicht berechtigt." }, { status: 403 }); } return null; } const CODE_PATTERN = /^[a-z]{2,5}$/; export async function GET(request: Request) { try { await requireAdmin(request); } catch (err) { const res = errorResponse(err); if (res) return res; throw err; } const languages = await listLanguages(); const hash = currentTemplateHash(); const items = await Promise.all( languages.map(async (lang) => { if (lang.is_base) { return { code: lang.code, label: lang.label, is_base: true, active: lang.active, percent: 100, outdated: false, missingCount: 0, }; } const rows = await listTranslations(lang.code); const translated = Object.fromEntries(rows.map((r) => [r.key, r.value])); const completeness = computeCompleteness(translated); return { code: lang.code, label: lang.label, is_base: false, active: lang.active, percent: completeness.percent, outdated: lang.template_version !== hash, missingCount: completeness.missingKeys.length, }; }) ); return NextResponse.json(items); } export async function POST(request: Request) { try { await requireAdmin(request); } catch (err) { const res = errorResponse(err); if (res) return res; throw err; } const body = await request.json().catch(() => null); const code = String(body?.code ?? "").trim().toLowerCase(); const label = String(body?.label ?? "").trim(); if (!CODE_PATTERN.test(code) || !label) { return NextResponse.json({ error: "invalid_input" }, { status: 400 }); } try { const language = await createLanguage(code, label); return NextResponse.json(language, { status: 201 }); } catch (err) { if (err instanceof Error && /duplicate key/i.test(err.message)) { return NextResponse.json({ error: "duplicate_code" }, { status: 409 }); } throw err; } }