From 6eea814fad1d81cdc6ac35060d43ad0f372136a4 Mon Sep 17 00:00:00 2001 From: Kenzo Date: Tue, 9 Dec 2025 17:08:36 +0100 Subject: [PATCH] feat: Add gallery management to admin panel + system documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - Gallery upload, delete, reorder (drag & drop) - Preview images in admin panel - Same upload flow as events (WebP conversion via sharp) - Complete system documentation explaining: - Architecture (Frontend/Backend/Volume) - Upload flow (Multipart β†’ Sharp β†’ WebP β†’ Storage) - Why SQLite works on Fly.io (persistent volumes) - Troubleshooting guide Admin now has full CRUD for both Events and Gallery! --- SYSTEM_ERKLAERUNG.md | 220 ++++++++++++++++++++++++++++++++++++++++++ src/pages/admin.astro | 149 ++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 SYSTEM_ERKLAERUNG.md diff --git a/SYSTEM_ERKLAERUNG.md b/SYSTEM_ERKLAERUNG.md new file mode 100644 index 0000000..cb70757 --- /dev/null +++ b/SYSTEM_ERKLAERUNG.md @@ -0,0 +1,220 @@ +# 🎯 Gallus Pub CMS - System-ErklΓ€rung + +## πŸ“ Architektur-Überblick + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend β”‚ β”‚ Backend CMS β”‚ β”‚ Fly.io Volume β”‚ +β”‚ (Astro SSG) │◄───────── (Fastify API) │◄───────── /app/data/ β”‚ +β”‚ gallus-pub.ch β”‚ fetch β”‚ cms.gallus-pub.chβ”‚ mount β”‚ - SQLite DB β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ - images/ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## πŸ”„ Wie funktioniert der Upload-Flow? + +### 1. **Admin lΓ€dt Bild hoch** (admin.astro) +``` +User wΓ€hlt Bild β†’ uploadImage() β†’ POST /api/gallery/upload +``` + +### 2. **Backend verarbeitet Upload** (backend/src/routes/gallery.ts) +```javascript +// Line 60-134 in gallery.ts +1. Empfange Multipart-File +2. Validiere Mimetype (nur images/*) +3. Lese Stream in Buffer +4. sharp() konvertiert zu WebP: + - Auto-rotate (EXIF) + - Resize auf max 1600px + - WebP quality 82% +5. Speichere in /app/data/images/gallery/ +6. Erstelle DB-Eintrag mit imageUrl +7. Return imageUrl an Frontend +``` + +### 3. **Bilder werden serviert** (backend/src/index.ts) +```javascript +// Line 63-69 +fastifyStatic β†’ /static/ β†’ /app/data/ +``` + +Beispiel: +- Bild liegt in: `/app/data/images/gallery/miyma9zc-8he1di.webp` +- URL ist: `/images/gallery/miyma9zc-8he1di.webp` +- Wird serviert als: `https://cms.gallus-pub.ch/static/images/gallery/miyma9zc-8he1di.webp` + +### 4. **Frontend zeigt Bilder** (src/pages/index.astro) +```javascript +// Line 30-43 +1. Fetch von /api/gallery/public +2. Map imageUrl: `${API_BASE}${img.imageUrl}` +3. Result: https://cms.gallus-pub.ch/static/images/gallery/xyz.webp +``` + +## πŸ’Ύ Warum funktioniert SQLite mit Fly.io? + +**Fly.io Volumes** sind persistente Speicher: +- Gemountet als: `/app/data/` +- Konfiguration in: `backend/fly.toml` (Line 40-42) +- Bleibt bei Restarts/Deploys erhalten + +```toml +[mounts] + source = "gallus_data" + destination = "/app/data" +``` + +### Was liegt wo? + +``` +/app/data/ +β”œβ”€β”€ gallus_cms.db # SQLite Datenbank +β”œβ”€β”€ gallus_cms.db-wal # Write-Ahead Log +β”œβ”€β”€ gallus_cms.db-shm # Shared Memory +└── images/ + β”œβ”€β”€ events/ + β”‚ β”œβ”€β”€ event_karaoke.webp + β”‚ └── ... + └── gallery/ + β”œβ”€β”€ Gallery1.webp + └── ... +``` + +## πŸ”’ Datenfluss im Detail + +### Event erstellen: +``` +1. Admin: Bild auswΓ€hlen + Formular ausfΓΌllen +2. uploadImage(file) β†’ /api/gallery/upload + ↓ +3. Backend: + - Sharp konvertiert β†’ WebP + - Speichert in /app/data/images/gallery/ + - Returnt: { image: { imageUrl: "/images/gallery/xyz.webp" } } + ↓ +4. Admin: POST /api/events + Body: { title, date, description, imageUrl: "/images/gallery/xyz.webp" } + ↓ +5. Backend: + - INSERT INTO events (imageUrl = "/images/gallery/xyz.webp") + ↓ +6. Frontend: GET /api/events/public + - Fetcht Events aus DB + - Mapped imageUrl zu voller URL + - Zeigt an: +``` + +### Warum separate Uploads fΓΌr Gallery? + +Events nutzen den Gallery-Upload, weil: +- Beide brauchen WebP-Konvertierung +- Beide nutzen gleichen Storage +- Vermeidet Code-Duplikation +- Gallery kann eigenstΓ€ndige Bildergalerie haben + +## 🎨 Admin-Panel Features + +### Events-Verwaltung: +- βœ… Event erstellen (mit Bild-Upload) +- βœ… Events auflisten +- βœ… Events lΓΆschen +- βœ… Reihenfolge Γ€ndern (Drag & Drop) +- βœ… Events verΓΆffentlichen/verstecken (isPublished) + +### Gallery-Verwaltung: (NEU!) +- βœ… Bild hochladen +- βœ… Gallery auflisten +- βœ… Bilder lΓΆschen +- βœ… Reihenfolge Γ€ndern (Drag & Drop) +- βœ… Bilder verΓΆffentlichen/verstecken (isPublished) + +### Publish: +- Git-Integration (fΓΌr statische Seiten-Updates) +- Commit & Push zu Repository + +## πŸš€ Deployment-Prozess + +### Was passiert beim Deploy? + +1. **Woodpecker CI** triggered bei Push zu `main` +2. **Frontend Deploy** (gallus-pub): + - Build Astro SSG + - Deploy zu Fly.io + +3. **Backend Deploy** (gallus-cms-backend): + - Docker Build: + ```dockerfile + # Backend-Code kompilieren + npm run build β†’ dist/ + + # Migrierte Bilder einpacken + COPY backend/data/images β†’ /app/migration-images + + # Migration-Script kopieren + COPY migrate-production.js β†’ /app/ + ``` + - Deploy zu Fly.io + - **Volume bleibt erhalten** (SQLite DB + hochgeladene Bilder) + +4. **Nach erstem Deploy**: Migration ausfΓΌhren + ```bash + fly ssh console -a gallus-cms-backend + node migrate-production.js + ``` + - Kopiert Bilder: `/app/migration-images/` β†’ `/app/data/images/` + - BefΓΌllt DB mit Event/Gallery-EintrΓ€gen + +## πŸ” Troubleshooting + +### "Bilder werden nicht angezeigt" + +**PrΓΌfe:** +1. Backend logs: `fly logs -a gallus-cms-backend` +2. Bild existiert: `fly ssh console -a gallus-cms-backend` + ```bash + ls -la /app/data/images/gallery/ + ``` +3. DB-Eintrag korrekt: + ```bash + sqlite3 /app/data/gallus_cms.db + SELECT * FROM gallery_images; + ``` +4. Static-Route funktioniert: + ```bash + curl https://cms.gallus-pub.ch/static/images/gallery/xyz.webp + ``` + +### "SQLite locked" Fehler + +- Nur ein Writer zur Zeit erlaubt +- Bei hohem Traffic: Wechsel zu PostgreSQL empfohlen +- FΓΌr Gallus Pub: ausreichend (wenig Writes) + +### "Volume voll" + +```bash +fly volumes list -a gallus-cms-backend +fly volumes extend -s +``` + +## πŸ“ Wichtige Dateien + +| Datei | Zweck | +|-------|-------| +| `backend/src/routes/gallery.ts` | Gallery-Upload & CRUD | +| `backend/src/routes/events.ts` | Events CRUD + Public API | +| `backend/src/index.ts` | Static File Serving | +| `backend/migrate-production.js` | Initiale Daten-Migration | +| `src/pages/admin.astro` | Admin-Interface | +| `src/pages/index.astro` | Frontend (fetched von API) | +| `backend/fly.toml` | Backend Fly.io Config | +| `fly.toml` | Frontend Fly.io Config | + +## 🎯 NΓ€chste Schritte + +1. βœ… Gallery-Verwaltung implementiert +2. ⏳ Migration ausfΓΌhren (nach Deploy) +3. ⏳ Testen: Bild hochladen β†’ Frontend anzeigen +4. πŸ“‹ Optional: Image-Editing Features +5. πŸ“‹ Optional: Bulk-Upload fΓΌr Gallery diff --git a/src/pages/admin.astro b/src/pages/admin.astro index 8570387..c52ceab 100644 --- a/src/pages/admin.astro +++ b/src/pages/admin.astro @@ -68,6 +68,28 @@ const title = 'Admin'; + +