cms: headless CMS vor Hugo (Supabase + Node-API + React-Admin)

All-in-One docker-compose-Stack (Muster von RAPPORT-SERVER gespiegelt):
db/auth/rest/kong + cms-Service (Node-API + Hugo-Binary 0.161.1 + Admin-SPA).

- DB-backed: posts-Tabelle kanonisch, MD ist generiertes Artefakt
- echte Hugo-Vorschau via draft:true + --buildDrafts → /_preview
- Publish: DB → content/library/<section>/<slug>.md → hugo build → live
- Bild-Upload nach static/images/, Supabase-Auth schützt /api/*
- Proxmox-LXC-Script: legt Container an, generiert Secrets, startet Stack

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 00:21:04 +02:00
parent 7a5be9250a
commit 60e5ef6844
31 changed files with 3616 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
import { supabase } from './supabase.js';
// Ruft die CMS-API (gleiche Origin) mit dem aktuellen Supabase-Token auf.
async function call(path, options = {}) {
const { data } = await supabase.auth.getSession();
const token = data?.session?.access_token;
const res = await fetch(`/api${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers,
},
});
const json = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`);
return json;
}
// Datei-Upload: kein JSON, der Browser setzt den multipart-Header selbst.
async function uploadFile(file) {
const { data } = await supabase.auth.getSession();
const token = data?.session?.access_token;
const form = new FormData();
form.append('file', file);
const res = await fetch('/api/upload', {
method: 'POST',
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: form,
});
const json = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`);
return json;
}
export const api = {
listPosts: () => call('/posts'),
createPost: (post) => call('/posts', { method: 'POST', body: JSON.stringify(post) }),
updatePost: (id, post) => call(`/posts/${id}`, { method: 'PUT', body: JSON.stringify(post) }),
preview: (id) => call(`/preview/${id}`, { method: 'POST' }),
publish: (id) => call(`/publish/${id}`, { method: 'POST' }),
upload: uploadFile,
};