Files
Speisekarte/lib/branding.ts
T
florian.wilde a9a9cf7ba4 v1.5: Alle Manager-Komponenten auf eigene API-Routen umgestellt
Neue interne Route Handler unter app/api/ (dishes inkl. Bulk-Delete,
sections, allergens, additives, menu-entries, dish-suggestions,
settings, admin/branding) ersetzen den direkten Browser→Postgrest-
Zugriff der bisherigen Supabase-Anbindung. Jede Route prüft Autorisierung
selbst (lib/auth/session.ts requireRole) statt sich auf Postgres-RLS zu
verlassen – ersetzt 1:1 die bisherigen Policies (öffentlich lesbar /
staff+admin schreiben / nur admin für Einstellungen & Benutzer).

Rezeptvorschlag-Dublettencheck (SuggestionForm) läuft jetzt zwingend
serverseitig im POST-Handler, da es keinen Browser→Postgres-Pfad mehr
gibt. "Passkey annehmen" (SuggestionReview) läuft transaktional
(Gericht anlegen + Vorschlag löschen in einem Schritt).

Logo/Favicon: neuer lokaler Dateispeicher (lib/branding.ts, Docker-
Volume) statt Supabase-Storage-Bucket, ausgeliefert über
app/branding/[filename]. logoUrl()/faviconUrl() liefern jetzt einfache
relative Pfade statt einer fest verdrahteten Supabase-Storage-URL.

DeepL-Route (api/translate) und Benutzerverwaltung (api/admin/users)
laufen jetzt gegen die users-Tabelle statt Supabase Auth.

Alle "use client"-Manager-Komponenten (DishManager, WeekEditor,
SectionManager, CodeListManager, SuggestionForm/Review, UserManager,
DesignEditor) rufen ihre jeweiligen /api/-Routen per fetch() statt den
Supabase-Browser-Client zu nutzen.
2026-07-18 11:01:52 +02:00

49 lines
1.7 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 "server-only";
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import path from "node:path";
// Ersetzt den früheren Supabase-Storage-Bucket "branding" durch ein
// lokales Verzeichnis (Docker-Volume in Produktion, s. docker-compose.yml).
export function brandingDir(): string {
// turbopackIgnore: process.cwd() ist hier dynamisch, aber der Pfad
// bleibt statisch unter "data/branding" – ohne den Kommentar versucht
// Turbopack sonst, das gesamte Projektverzeichnis nach möglichen
// dynamischen require()-Zielen zu durchsuchen (harmlos, aber unnötig).
return (
process.env.BRANDING_DIR ??
path.join(/*turbopackIgnore: true*/ process.cwd(), "data", "branding")
);
}
// Nur einfache, von uns selbst vergebene Dateinamen zulassen
// (`${kind}-${Date.now()}.${ext}`) – verhindert Path-Traversal über
// Dateinamen aus der DB-Spalte logo_path/favicon_path.
const SAFE_FILENAME = /^[a-zA-Z0-9_.-]+$/;
export function isSafeFilename(filename: string): boolean {
return SAFE_FILENAME.test(filename) && !filename.includes("..");
}
export async function saveBrandingFile(filename: string, data: Buffer): Promise<void> {
const dir = brandingDir();
await mkdir(dir, { recursive: true });
await writeFile(path.join(dir, filename), data);
}
export async function readBrandingFile(filename: string): Promise<Buffer | null> {
if (!isSafeFilename(filename)) return null;
try {
return await readFile(path.join(brandingDir(), filename));
} catch {
return null;
}
}
export async function removeBrandingFile(filename: string): Promise<void> {
try {
await unlink(path.join(brandingDir(), filename));
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
}
}