"use server" import { randomBytes } from "node:crypto" import * as z from "zod" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" import { createClient } from "@/lib/supabase/server" import { verifySession } from "@/lib/supabase/dal" import { resolveContent } from "../lib/content" import { SHARE_EXPIRY_OPTIONS } from "../lib/preview" import { getTemplate } from "../lib/registry" export type VariantActionResult = { error: string } | { success: true } export type ShareResult = { error: string } | { token: string | null; expiresAt: string | null } const IdSchema = z.string().uuid() const SaveSchema = z.object({ name: z.string().trim().min(1, { error: "Bitte einen Namen für die Variante eingeben." }).max(120), clientId: z.string().uuid().nullable(), content: z.unknown(), }) const ShareSchema = z.object({ enabled: z.boolean(), expiresInDays: z.number().refine((days) => SHARE_EXPIRY_OPTIONS.some((option) => option.days === days)), regenerate: z.boolean().optional(), }) async function requireAdmin() { const session = await verifySession() return session.isAdmin ? session : null } function firstIssue(error: z.ZodError) { return error.issues[0]?.message ?? "Ungültige Eingabe." } // Nicht erratbar, URL-tauglich: 24 Byte ergeben 32 Zeichen function newShareToken() { return randomBytes(24).toString("base64url") } function revalidateTemplates() { revalidatePath("/admin/templates", "layout") } export async function createVariant(templateKey: string): Promise<{ error: string } | undefined> { if (!(await requireAdmin())) return { error: "Keine Berechtigung." } const def = getTemplate(templateKey) if (!def) return { error: "Vorlage nicht gefunden." } const supabase = await createClient() const { data, error } = await supabase .from("template_variants") .insert({ template_key: def.key, name: `${def.name} – Entwurf`, content: resolveContent(def, {}) }) .select("id") .single() if (error || !data) return { error: "Variante konnte nicht angelegt werden." } revalidateTemplates() redirect(`/admin/templates/${data.id}`) } export async function saveVariant(id: string, input: z.input): Promise { if (!(await requireAdmin())) return { error: "Keine Berechtigung." } if (!IdSchema.safeParse(id).success) return { error: "Ungültige Variante." } const parsed = SaveSchema.safeParse(input) if (!parsed.success) return { error: firstIssue(parsed.error) } const supabase = await createClient() const { data: current } = await supabase.from("template_variants").select("template_key").eq("id", id).maybeSingle() const def = current ? getTemplate(current.template_key) : undefined if (!def) return { error: "Variante nicht gefunden." } const { error } = await supabase .from("template_variants") .update({ name: parsed.data.name, client_id: parsed.data.clientId, content: resolveContent(def, parsed.data.content), }) .eq("id", id) if (error) return { error: "Änderungen konnten nicht gespeichert werden." } revalidateTemplates() return { success: true } } export async function updateShare(id: string, input: z.input): Promise { if (!(await requireAdmin())) return { error: "Keine Berechtigung." } if (!IdSchema.safeParse(id).success) return { error: "Ungültige Variante." } const parsed = ShareSchema.safeParse(input) if (!parsed.success) return { error: "Ungültige Freigabe-Einstellung." } const { enabled, expiresInDays, regenerate } = parsed.data const supabase = await createClient() const { data: current } = await supabase.from("template_variants").select("share_token").eq("id", id).maybeSingle() if (!current) return { error: "Variante nicht gefunden." } const token = enabled ? (regenerate || !current.share_token ? newShareToken() : current.share_token) : null const expiresAt = enabled && expiresInDays > 0 ? new Date(Date.now() + expiresInDays * 86_400_000).toISOString() : null const { error } = await supabase .from("template_variants") .update({ share_token: token, share_expires_at: expiresAt }) .eq("id", id) if (error) return { error: "Freigabe konnte nicht gespeichert werden." } revalidateTemplates() return { token, expiresAt } } export async function duplicateVariant(id: string): Promise<{ error: string } | undefined> { if (!(await requireAdmin())) return { error: "Keine Berechtigung." } if (!IdSchema.safeParse(id).success) return { error: "Ungültige Variante." } const supabase = await createClient() const { data: source } = await supabase .from("template_variants") .select("name, template_key, client_id, content") .eq("id", id) .maybeSingle() if (!source) return { error: "Variante nicht gefunden." } // Der Freigabe-Link wird bewusst nicht mitkopiert const { data, error } = await supabase .from("template_variants") .insert({ ...source, name: `${source.name} (Kopie)`.slice(0, 120) }) .select("id") .single() if (error || !data) return { error: "Variante konnte nicht dupliziert werden." } revalidateTemplates() redirect(`/admin/templates/${data.id}`) } export async function deleteVariant(id: string): Promise { if (!(await requireAdmin())) return { error: "Keine Berechtigung." } if (!IdSchema.safeParse(id).success) return { error: "Ungültige Variante." } const supabase = await createClient() const { error } = await supabase.from("template_variants").delete().eq("id", id) if (error) return { error: "Variante konnte nicht gelöscht werden." } revalidateTemplates() return { success: true } }