cms: Rollen + Kollaboration (Admin sieht alles, Autoren nur eigene/geteilte)

- ADMIN_EMAILS (.env) = Admins, sehen/bearbeiten alles
- Autor:innen sehen nur Einträge mit ihrer Mail unter `authors:`; Ersteller wird
  beim Anlegen automatisch Autor
- Kollaboration: Feld „Autor:innen" im Editor → mehrere Mails = gemeinsamer Zugriff
- API erzwingt Zugriff bei list/read/save (403 ohne Recht)
- ADMIN_EMAILS in compose + LXC-Script (fragt Admin-Mail ab)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 12:22:06 +02:00
parent 35c2a122ae
commit bd4b470877
8 changed files with 86 additions and 16 deletions
+7 -1
View File
@@ -28,7 +28,7 @@ const EMPTY = {
isNew: true, path: '', type: 'beitrag', section: 'software', slug: '',
title: '', date: new Date().toISOString().slice(0, 10), weight: '',
color: '', layout: 'text', tags: '', summary: '', description: '',
cover_image: '', external: '', toc: false, draft: true, body: '',
cover_image: '', external: '', authors: '', toc: false, draft: true, body: '',
};
export default function App() {
@@ -264,6 +264,9 @@ function Editor({ initial, onSaved, onMsg }) {
<label>Cover-Bild<input value={f.cover_image} onChange={set('cover_image')} placeholder="/images/…jpg" /></label>
<label>Externer Link<input value={f.external} onChange={set('external')} placeholder="https://…" /></label>
</div>
<label>Autor:innen (E-Mails, Komma für gemeinsamen Zugriff)
<input value={f.authors} onChange={set('authors')} placeholder="du@…, kollege@…" />
</label>
<div className="rich">
<RichEditor value={f.body} onChange={(body) => setF((p) => ({ ...p, body }))}
@@ -376,6 +379,7 @@ function fromRead(r) {
tags: Array.isArray(fm.tags) ? fm.tags.join(', ') : '',
summary: fm.summary || '', description: fm.description || '',
cover_image: fm.cover_image || '', external: fm.external || '',
authors: Array.isArray(fm.authors) ? fm.authors.join(', ') : (fm.authors || ''),
toc: !!fm.toc, draft: !!fm.draft, body: r.body || '',
};
}
@@ -391,6 +395,8 @@ function buildFrontmatter(f) {
if (f.layout) fm.layout = f.layout;
if (f.external) fm.external = f.external;
if (f.color) fm.color = f.color;
const authors = f.authors ? f.authors.split(',').map((t) => t.trim()).filter(Boolean) : [];
if (authors.length) fm.authors = authors;
if (f.toc) fm.toc = true;
if (f.draft) fm.draft = true;
return fm;