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.
This commit is contained in:
2026-07-18 11:01:52 +02:00
parent 28c9ec15c8
commit a9a9cf7ba4
58 changed files with 1184 additions and 1858 deletions
+12 -33
View File
@@ -1,13 +1,11 @@
"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { useCallback, useEffect, useState } from "react";
import { formatMessage } from "@/lib/i18n/shared";
import { useDict } from "@/components/i18n/LocaleProvider";
import type { DishSuggestion } from "@/lib/types";
export default function SuggestionReview() {
const supabase = useMemo(() => createClient(), []);
const dict = useDict();
const [items, setItems] = useState<DishSuggestion[]>([]);
const [loading, setLoading] = useState(true);
@@ -16,15 +14,12 @@ export default function SuggestionReview() {
const [busyId, setBusyId] = useState<string | null>(null);
const load = useCallback(async () => {
const { data, error } = await supabase
.from("dish_suggestions")
.select("*")
.order("created_at", { ascending: false });
if (error)
setError(formatMessage(dict.suggestions.loadError, { message: error.message }));
else setItems(data as DishSuggestion[]);
const res = await fetch("/api/dish-suggestions");
if (!res.ok)
setError(formatMessage(dict.suggestions.loadError, { message: String(res.status) }));
else setItems(await res.json());
setLoading(false);
}, [supabase, dict]);
}, [dict]);
useEffect(() => {
load();
@@ -34,25 +29,9 @@ export default function SuggestionReview() {
setBusyId(item.id);
setError(null);
setInfo(null);
const { error: insertError } = await supabase.from("dishes").insert({
name: item.name,
price: 0,
});
if (insertError) {
setError(
formatMessage(dict.suggestions.actionError, { message: insertError.message })
);
setBusyId(null);
return;
}
const { error: deleteError } = await supabase
.from("dish_suggestions")
.delete()
.eq("id", item.id);
if (deleteError) {
setError(
formatMessage(dict.suggestions.actionError, { message: deleteError.message })
);
const res = await fetch(`/api/dish-suggestions/${item.id}/accept`, { method: "POST" });
if (!res.ok) {
setError(formatMessage(dict.suggestions.actionError, { message: String(res.status) }));
setBusyId(null);
return;
}
@@ -67,9 +46,9 @@ export default function SuggestionReview() {
setBusyId(item.id);
setError(null);
setInfo(null);
const { error } = await supabase.from("dish_suggestions").delete().eq("id", item.id);
if (error) {
setError(formatMessage(dict.suggestions.actionError, { message: error.message }));
const res = await fetch(`/api/dish-suggestions/${item.id}`, { method: "DELETE" });
if (!res.ok) {
setError(formatMessage(dict.suggestions.actionError, { message: String(res.status) }));
} else {
setInfo(dict.suggestions.rejected);
}