Files
Gallus_Pub/SYSTEM_ERKLAERUNG.md
Kenzo 6eea814fad
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: Add gallery management to admin panel + system documentation
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!
2025-12-09 17:08:36 +01:00

6.3 KiB

🎯 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)

// 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)

// 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)

// 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
[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: <img src="https://cms.gallus-pub.ch/static/images/gallery/xyz.webp">

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)
  • 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:
      # 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

    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
    ls -la /app/data/images/gallery/
    
  3. DB-Eintrag korrekt:
    sqlite3 /app/data/gallus_cms.db
    SELECT * FROM gallery_images;
    
  4. Static-Route funktioniert:
    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"

fly volumes list -a gallus-cms-backend
fly volumes extend <volume-id> -s <new-size>

📁 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