8404165f5c
- auth: Supabase-JWT lokal verifizieren (hono/jwt, HS256) statt GoTrue- Roundtrip pro Request; JWT_SECRET in cms-env, Remote-Fallback wenn ungesetzt - dialog: comment_stats-View (group by thread) ersetzt Full-Table-Scan + JS-Aggregation bei jedem Forum-Aufruf - ops: scripts/backup-db.sh (pg_dump, rotiert) + täglicher Cron im Proxmox- Script — Dialog-Daten liegen nur in Postgres, nicht in Git - security: Rate-Limit auf Schreib-Endpunkte (/api non-GET, 60/min je Nutzer) - perf: Cache-Control (1 Woche) auf statische Assets, HTML bleibt frisch Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
5.3 KiB
SQL
112 lines
5.3 KiB
SQL
-- OPENBUREAU CMS — posts-Tabelle. In den Supabase-Stack einspielen
|
|
-- (SQL-Editor oder psql). Spalten bilden das Hugo-Frontmatter ab.
|
|
|
|
create extension if not exists "pgcrypto";
|
|
|
|
create table if not exists public.posts (
|
|
id uuid primary key default gen_random_uuid(),
|
|
section text not null, -- buerofuehrung | software | theorie
|
|
slug text not null, -- a-z0-9- (Dateiname ohne .md)
|
|
title text not null,
|
|
date date not null default current_date,
|
|
weight int,
|
|
tags text[] default '{}',
|
|
summary text,
|
|
cover_image text,
|
|
layout text, -- z.B. "image" | "text"
|
|
external text, -- externer Link (wie RAPPORT)
|
|
color text, -- z.B. "kusa" | "yuyake"
|
|
body text default '', -- Markdown-Inhalt
|
|
status text not null default 'draft', -- draft | published
|
|
author text,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
published_at timestamptz,
|
|
unique (section, slug)
|
|
);
|
|
|
|
create index if not exists posts_status_idx on public.posts (status);
|
|
create index if not exists posts_section_idx on public.posts (section);
|
|
|
|
-- RLS aktivieren; die api nutzt den Service-Key (umgeht RLS). Wenn das
|
|
-- Frontend später direkt liest, hier gezielte Policies ergänzen.
|
|
alter table public.posts enable row level security;
|
|
|
|
-- ── Dialog / Diskussionen ───────────────────────────────────────────────
|
|
-- Thread = Pfad des Beitrags (z.B. /library/software/stack/). Flache Wortmeldungen
|
|
-- mit optionalem Bezug (parent_id). Idempotent — auf bestehende DB anwendbar.
|
|
create table if not exists public.comments (
|
|
id uuid primary key default gen_random_uuid(),
|
|
thread text not null,
|
|
parent_id uuid references public.comments(id) on delete cascade,
|
|
user_id uuid,
|
|
author_name text,
|
|
author_avatar text,
|
|
body text not null,
|
|
created_at timestamptz not null default now(),
|
|
deleted boolean not null default false
|
|
);
|
|
-- Position/Rolle bei OPENBUREAU (optional, neben dem Namen angezeigt).
|
|
alter table public.comments add column if not exists author_role text;
|
|
create index if not exists comments_thread_idx on public.comments (thread, created_at);
|
|
alter table public.comments enable row level security;
|
|
grant all on public.comments to anon, authenticated, service_role;
|
|
|
|
-- Aggregat je Thread (Anzahl + letzte Aktivität). Spart der API den Full-Table-
|
|
-- Scan + JS-Aggregation bei jedem Forum-Aufruf; Postgres zählt direkt.
|
|
create or replace view public.comment_stats as
|
|
select thread, count(*)::int as count, max(created_at) as last
|
|
from public.comments
|
|
where not deleted
|
|
group by thread;
|
|
grant select on public.comment_stats to service_role;
|
|
|
|
-- ── Foren / Subforen ────────────────────────────────────────────────────
|
|
-- Kategorien, in denen Threads leben. Admin-verwaltet. `kind=library` ist die
|
|
-- Sonder-Kategorie, in der die Library-Beiträge automatisch als Threads landen.
|
|
create table if not exists public.forums (
|
|
id uuid primary key default gen_random_uuid(),
|
|
slug text not null unique,
|
|
name text not null,
|
|
description text default '',
|
|
color text, -- optionale Akzentfarbe
|
|
sort int not null default 0,
|
|
kind text not null default 'forum', -- forum | library
|
|
created_at timestamptz not null default now()
|
|
);
|
|
alter table public.forums enable row level security;
|
|
grant all on public.forums to anon, authenticated, service_role;
|
|
|
|
-- ── Threads (Diskussionen) ──────────────────────────────────────────────
|
|
-- key = stabiler Bezeichner, den comments.thread referenziert:
|
|
-- Forum-Thread → 't/<uuid>'
|
|
-- Library-Beitrag → Beitragspfad (z.B. /library/software/stack/)
|
|
create table if not exists public.threads (
|
|
id uuid primary key default gen_random_uuid(),
|
|
forum_id uuid references public.forums(id) on delete cascade,
|
|
key text not null unique,
|
|
title text not null,
|
|
url text, -- Ziel-Link (Library: Beitragspfad)
|
|
kind text not null default 'forum', -- forum | library
|
|
author_name text,
|
|
user_id uuid,
|
|
locked boolean not null default false,
|
|
deleted boolean not null default false,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
create index if not exists threads_forum_idx on public.threads (forum_id);
|
|
alter table public.threads enable row level security;
|
|
grant all on public.threads to anon, authenticated, service_role;
|
|
|
|
-- Seed-Kategorien (idempotent; im Admin umbenenn-/erweiterbar).
|
|
insert into public.forums (slug, name, sort, kind) values
|
|
('allgemein', 'Allgemein', 10, 'forum'),
|
|
('projekte', 'Projekte', 20, 'forum'),
|
|
('technik', 'Technik', 30, 'forum'),
|
|
('off-topic', 'Off-Topic', 40, 'forum')
|
|
on conflict (slug) do nothing;
|
|
-- Sonder-Kategorie für die Library-Beiträge.
|
|
insert into public.forums (slug, name, sort, kind) values
|
|
('beitraege', 'Beiträge', 0, 'library')
|
|
on conflict (slug) do nothing;
|