# π― 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