All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
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!
6.3 KiB
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">
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?
-
Woodpecker CI triggered bei Push zu
main -
Frontend Deploy (gallus-pub):
- Build Astro SSG
- Deploy zu Fly.io
-
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)
- Docker Build:
-
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
- Kopiert Bilder:
🔍 Troubleshooting
"Bilder werden nicht angezeigt"
Prüfe:
- Backend logs:
fly logs -a gallus-cms-backend - Bild existiert:
fly ssh console -a gallus-cms-backendls -la /app/data/images/gallery/ - DB-Eintrag korrekt:
sqlite3 /app/data/gallus_cms.db SELECT * FROM gallery_images; - 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
- ✅ Gallery-Verwaltung implementiert
- ⏳ Migration ausführen (nach Deploy)
- ⏳ Testen: Bild hochladen → Frontend anzeigen
- 📋 Optional: Image-Editing Features
- 📋 Optional: Bulk-Upload für Gallery