790141bafe
Neue Informationsarchitektur: - ARCHIV (/archiv) = die fertigen Texte (vormals Library): Essays mit Byline, Quellen/Zitieren, Dialog, Versionsverlauf. Section "archiv". - LIBRARY (/library) = das verlinkte Werkstattwissen (vormals Wiki): zwei- spaltig mit Gruppen-Navigation + Filter. Section "library". Umgesetzt: - content/ + layouts/ verschoben (git mv), Menü (ARCHIV+LIBRARY, kein WIKI), Startseiten-Journal zieht jetzt Section "archiv", Querverweise umgeschrieben (/library→/archiv, /wiki→/library). - CMS: files.js klassifiziert archiv/<sec>→beitrag, library/→biblio; stats.js + Admin (Typ "Library-Seite", KIND_LABEL, Pfade) nachgezogen. - single.html: Byline/Provenance/Dialog an Section "archiv" gebunden. - Beide Header zentriert (section-header) — einheitlicher Look. - Interne Dialog-Werte (thread.kind='library', Forum "Beiträge") unverändert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.7 KiB
JavaScript
48 lines
1.7 KiB
JavaScript
import { Hono } from 'hono';
|
|
import { supabase } from '../supabase.js';
|
|
import { listEntries } from '../files.js';
|
|
import { requireAdmin, roleOf } from '../auth.js';
|
|
|
|
// Kennzahlen für die Admin-Übersicht. Nur Admins; rein lesend.
|
|
const stats = new Hono();
|
|
stats.use('*', requireAdmin);
|
|
|
|
stats.get('/', async (c) => {
|
|
// Inhalte aus dem Dateisystem zählen.
|
|
const content = { beitraege: 0, entwuerfe: 0, library: 0, seiten: 0, rubriken: 0 };
|
|
try {
|
|
for (const e of await listEntries()) {
|
|
if (e.kind === 'beitrag') { content.beitraege++; if (e.draft) content.entwuerfe++; }
|
|
else if (e.kind === 'biblio') content.library++;
|
|
else if (e.kind === 'rubrik') content.rubriken++;
|
|
else content.seiten++;
|
|
}
|
|
} catch { /* Filesystem nicht lesbar → 0 */ }
|
|
|
|
// Nutzer nach Rolle.
|
|
const users = { total: 0, admin: 0, editor: 0, user: 0 };
|
|
try {
|
|
const { data } = await supabase.auth.admin.listUsers();
|
|
for (const u of data?.users || []) { users.total++; users[roleOf(u)] = (users[roleOf(u)] || 0) + 1; }
|
|
} catch { /* GoTrue nicht erreichbar */ }
|
|
|
|
// Dialog-Zähler (effizient: head + count, keine Zeilen laden).
|
|
const count = async (table, filter) => {
|
|
try {
|
|
let q = supabase.from(table).select('*', { count: 'exact', head: true });
|
|
if (filter) q = filter(q);
|
|
const { count: n } = await q;
|
|
return n || 0;
|
|
} catch { return 0; }
|
|
};
|
|
const [forums, threads, comments] = await Promise.all([
|
|
count('forums'),
|
|
count('threads', (q) => q.eq('deleted', false)),
|
|
count('comments', (q) => q.eq('deleted', false)),
|
|
]);
|
|
|
|
return c.json({ content, users, dialog: { forums, threads, comments } });
|
|
});
|
|
|
|
export default stats;
|