dialog: Diskussionsplattform mit Foren, Rollen & Moderation + RLS-Fix

Auth/RLS-Fix (Schreiben gab 400):
- supabase.js: eigener supabaseAuth-Client für Login/Token-Check, damit
  signInWithPassword den Service-Daten-Client nicht prozessweit aufs
  User-Token umstellt (sonst lief insert als role=authenticated → RLS-Block).

Rollen (admin > editor > user):
- auth.js: roleOf() aus app_metadata.role + ADMIN_EMAILS, requireModerator.
- users.js: Rolle anzeigen/setzen über GoTrue app_metadata; .env-Admins fix.

Datenmodell (schema.sql):
- forums (Kategorien) + threads; Seed Allgemein/Projekte/Technik/Off-Topic
  und Sonder-Kategorie Beiträge. Library-Beiträge werden als Threads
  gespiegelt (dialog-store.syncLibrary).

API (routes/dialog.js, dialog-store.js):
- öffentlich: /api/forums, /api/forums/:slug, /api/recent, /api/thread
- eingeloggt: POST /api/threads (Thread starten, nur in Foren)
- Moderation: /api/mod/* (sperren/ausblenden), Admin: /api/admin/forums CRUD
- comments: Lock-Prüfung beim Schreiben, Moderation darf jede löschen.

Frontend:
- static/dialog.js: Router (Übersicht-Split-View | Forum | Thread),
  neuer Thread, Mod-Leiste, subtiles Login (dezente Zeile statt Formular).
- Admin-UI: Tabs Foren + Moderation, Rollen-Dropdown bei Autor:innen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 16:09:19 +02:00
parent 2749451107
commit bd85570259
14 changed files with 916 additions and 211 deletions
+11 -4
View File
@@ -8,7 +8,14 @@ if (!url || !key) {
process.exit(1);
}
// Service-Role-Key: server-seitig, umgeht RLS. Niemals ins Frontend geben.
export const supabase = createClient(url, key, {
auth: { persistSession: false, autoRefreshToken: false },
});
const opts = { auth: { persistSession: false, autoRefreshToken: false } };
// Daten-Client: Service-Role-Key, umgeht RLS. NUR für DB-Zugriffe (from/insert/…).
// Wichtig: hier niemals signInWithPassword aufrufen — das schaltet den
// Authorization-Header des Clients prozessweit auf das User-Token um (SIGNED_IN),
// wodurch anschließende Inserts als role=authenticated laufen und an RLS scheitern.
export const supabase = createClient(url, key, opts);
// Eigener Client nur für Auth (Login, Token-Prüfung). Getrennt, damit ein
// signInWithPassword den Daten-Client oben nicht „vergiftet". Niemals ins Frontend.
export const supabaseAuth = createClient(url, key, opts);