L1 Stammdaten: Dokumentenablage-Service und Download per documentId

storeFile (Allowlist, Magic Bytes, Größenlimits, Dateinamen-Normalisierung,
SHA-256, Versionierung über lineageId), FileScanner mit optionalem ClamAV-Hook,
Sichtbarkeits-/Scope-Autorisierung, Upload-Route und Umbau der Download-Route
von files/[...key] auf files/[documentId].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 12:26:27 +02:00
co-authored by Claude Opus 5
parent 1f8e6413fe
commit 49c5ad0e33
6 changed files with 719 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
"use server";
import { revalidatePath } from "next/cache";
import { moduleGuard } from "@/server/action-guard";
import { ctxFromGuard } from "@/server/services/context";
import { formObject, toActionError, type ActionState } from "@/server/api/action-state";
import { deleteDocument, updateDocumentMeta } from "@/server/services/documents/access";
import type { DocumentCategory, DocumentVisibility } from "@prisma/client";
const guard = moduleGuard("documents");
/** Only revalidate same-app paths passed by our own pages. */
function safePath(path: string): string {
return path.startsWith("/") && !path.startsWith("//") ? path.split("?")[0] : "/documents";
}
export async function updateDocumentAction(documentId: string, returnPath: string, _prev: ActionState, fd: FormData): Promise<ActionState> {
try {
const ctx = ctxFromGuard(await guard("document:write"));
const v = formObject(fd, ["title", "category", "visibility"]);
await updateDocumentMeta(ctx, documentId, {
title: v.title ?? null,
category: v.category as DocumentCategory | undefined,
visibility: v.visibility as DocumentVisibility | undefined,
});
} catch (err) {
return toActionError(err);
}
revalidatePath(safePath(returnPath));
revalidatePath("/documents");
return { status: "ok" };
}
export async function deleteDocumentAction(documentId: string, returnPath: string): Promise<ActionState> {
try {
const ctx = ctxFromGuard(await guard("document:write"));
await deleteDocument(ctx, documentId);
} catch (err) {
return toActionError(err);
}
revalidatePath(safePath(returnPath));
revalidatePath("/documents");
return { status: "ok" };
}