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