diff --git a/MIGRATION_README.md b/MIGRATION_README.md
new file mode 100644
index 0000000..194b1c6
--- /dev/null
+++ b/MIGRATION_README.md
@@ -0,0 +1,142 @@
+# Migration der alten Events und Gallery-Bilder
+
+## ✅ Was wurde migriert?
+
+### Events (7 Stück):
+- Karaoke (wiederkehrend)
+- Pub Quiz (wiederkehrend)
+- Schlager Hüttenzauber Karaoke
+- Adventskalender
+- Santa Karaoke-Party
+- Weihnachtsferien
+- Neujahrs-Apero
+
+### Gallery-Bilder (9 Stück):
+- Gallery1.webp bis Gallery9.webp
+
+## 📁 Wo liegen die Bilder?
+
+Alle Bilder wurden konvertiert und liegen jetzt in:
+- **Events:** `backend/data/images/events/`
+- **Gallery:** `backend/data/images/gallery/`
+
+Die Bilder wurden automatisch:
+- Von PNG/JPG/JPEG zu WebP konvertiert
+- Auf max. 1600px Breite skaliert
+- Mit 85% Qualität optimiert
+
+## 🚀 Deployment-Schritte
+
+### 1. Lokale Vorbereitung (bereits erledigt ✓)
+- ✓ Migrations-Script erstellt
+- ✓ Bilder konvertiert und in `backend/data/images/` kopiert
+- ✓ Public API-Endpunkte erstellt (`/api/events/public`, `/api/gallery/public`)
+- ✓ Frontend aktualisiert, um Events und Gallery dynamisch zu laden
+
+### 2. Auf Fly.io deployen
+
+Alle Änderungen committen und pushen:
+```bash
+git add .
+git commit -m "feat: Migrate old events and gallery images to CMS"
+git push origin main
+```
+
+Woodpecker CI wird automatisch beide Services deployen.
+
+### 3. Nach dem ersten Deploy - Datenbank initialisieren
+
+**Wichtig:** Die Bilder sind bereits im Repository in `backend/data/images/`, aber die Datenbank muss noch mit den Event- und Gallery-Einträgen befüllt werden.
+
+#### Via fly ssh (Empfohlen):
+
+```bash
+# In das Backend einloggen
+fly ssh console -a gallus-cms-backend
+
+# Prüfen ob Bilder da sind
+ls -la /app/data/images/events/
+ls -la /app/data/images/gallery/
+
+# Migrations-Script ausführen
+cd /app
+npm run migrate:old-data
+```
+
+#### Alternative: Manuell via Admin-Panel
+
+1. Gehe zu https://gallus-pub.ch/admin
+2. Melde dich an
+3. Für jedes Event:
+ - Klicke auf "Neues Event"
+ - Gib Titel, Datum und Beschreibung ein
+ - Statt Bild hochzuladen, trage manuell die imageUrl ein:
+ - z.B. `/images/events/event_karaoke.webp`
+ - Speichere das Event
+
+## 🔍 Verifikation
+
+Nach dem Deployment prüfen:
+
+1. **Frontend:** https://gallus-pub.ch/
+ - Events sollten angezeigt werden
+ - Gallery sollte Bilder zeigen
+
+2. **Admin:** https://gallus-pub.ch/admin
+ - Events können bearbeitet werden
+ - Neue Events können hinzugefügt werden
+
+3. **Backend Health:** https://cms.gallus-pub.ch/health
+ - Status sollte "ok" sein
+
+## 📝 Event-Daten für manuelles Einfügen
+
+Falls du die Events manuell via Admin-Panel einfügen möchtest:
+
+### Karaoke
+- **Titel:** Karaoke
+- **Datum:** 2025-12-31
+- **Beschreibung:** Bei uns gibt es Karaoke Mi-Sa!!
Seid ihr eine Gruppe und lieber unter euch? ..unseren 2.Stock kannst du auch mieten ;)
Reserviere am besten gleich per Whatsapp 077 232 27 70
+- **Bild-URL:** `/images/events/event_karaoke.webp`
+
+### Pub Quiz
+- **Titel:** Pub Quiz
+- **Datum:** 2025-12-31
+- **Beschreibung:** Jeden Freitag findet unser Pub Quiz statt. Gespielt wird tischweise in 3-4 Runden.
Jede Woche gibt es ein anderes Thema. Es geht um Ruhm und Ehre und zusätzlich werden die Sieger der Herzen durch das Publikum gekürt! <3
Auch Einzelpersonen sind herzlich willkommen!
*zum mitmachen minimum 1 Getränk konsumieren oder 5CHF
+- **Bild-URL:** `/images/events/event_pub-quiz.webp`
+
+### Schlager Hüttenzauber Karaoke
+- **Titel:** Schlager Hüttenzauber Karaoke
+- **Datum:** 2025-11-27
+- **Beschreibung:** Ab 19:00 Uhr Eintritt ist Frei! Reservieren unter 077 232 27 70
+- **Bild-URL:** `/images/events/event_schlager-karaoke.webp`
+
+### Adventskalender
+- **Titel:** Adventskalender
+- **Datum:** 2025-12-20
+- **Beschreibung:** Jeden Tag neue Überraschungen! Check unsere Social Media Stories!
+- **Bild-URL:** `/images/events/event_advents-kalender.webp`
+
+### Santa Karaoke-Party
+- **Titel:** Santa Karaoke-Party
+- **Datum:** 2025-12-06
+- **Beschreibung:** 🤶🏻🎅🏻Komme als Weihnachts-Mann/-Frau und bekomme einen Shot auf's Haus!🤶🏻🎅🏻
+- **Bild-URL:** `/images/events/event_santa_karaoke.webp`
+
+### Weihnachtsferien
+- **Titel:** Weihnachtsferien
+- **Datum:** 2025-12-21
+- **Beschreibung:** Wir sind ab 02.01.2026 wieder wie gewohnt für euch da! 🍀.
Für Anfragen WA 077 232 27 70 Antwort innerhalb 48h
+- **Bild-URL:** `/images/events/event_ferien.webp`
+
+### Neujahrs-Apero
+- **Titel:** Neujahrs-Apero
+- **Datum:** 2026-01-02
+- **Beschreibung:** 18:00-20:00 Uhr
+- **Bild-URL:** `/images/events/event_neujahrs-apero.webp`
+
+## ⚠️ Wichtige Hinweise
+
+1. **Bilder sind im Volume persistent:** Alle Bilder in `/app/data/` bleiben bei Restarts erhalten
+2. **Datenbank ist persistent:** Die SQLite-DB in `/app/data/gallus_cms.db` bleibt erhalten
+3. **Alte Bilder in `public/images/`:** Die alten Original-Bilder bleiben im Frontend-Repository, werden aber nicht mehr verwendet
diff --git a/backend/.gitignore b/backend/.gitignore
index e2f0e3e..7814f45 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -4,7 +4,9 @@ dist
*.log
.DS_Store
/tmp
-/data
-*.db
-*.db-wal
-*.db-shm
+/data/*.db
+/data/*.db-wal
+/data/*.db-shm
+/data/workspace
+# Allow images to be committed
+!/data/images
diff --git a/backend/data/images/events/event_advents-kalender.webp b/backend/data/images/events/event_advents-kalender.webp
new file mode 100644
index 0000000..35f826a
Binary files /dev/null and b/backend/data/images/events/event_advents-kalender.webp differ
diff --git a/backend/data/images/events/event_ferien.webp b/backend/data/images/events/event_ferien.webp
new file mode 100644
index 0000000..c282cd7
Binary files /dev/null and b/backend/data/images/events/event_ferien.webp differ
diff --git a/backend/data/images/events/event_karaoke.webp b/backend/data/images/events/event_karaoke.webp
new file mode 100644
index 0000000..4912672
Binary files /dev/null and b/backend/data/images/events/event_karaoke.webp differ
diff --git a/backend/data/images/events/event_neujahrs-apero.webp b/backend/data/images/events/event_neujahrs-apero.webp
new file mode 100644
index 0000000..4e1c1f2
Binary files /dev/null and b/backend/data/images/events/event_neujahrs-apero.webp differ
diff --git a/backend/data/images/events/event_pub-quiz.webp b/backend/data/images/events/event_pub-quiz.webp
new file mode 100644
index 0000000..4936ecb
Binary files /dev/null and b/backend/data/images/events/event_pub-quiz.webp differ
diff --git a/backend/data/images/events/event_santa_karaoke.webp b/backend/data/images/events/event_santa_karaoke.webp
new file mode 100644
index 0000000..129cf5a
Binary files /dev/null and b/backend/data/images/events/event_santa_karaoke.webp differ
diff --git a/backend/data/images/events/event_schlager-karaoke.webp b/backend/data/images/events/event_schlager-karaoke.webp
new file mode 100644
index 0000000..2ca22d9
Binary files /dev/null and b/backend/data/images/events/event_schlager-karaoke.webp differ
diff --git a/backend/data/images/gallery/Gallery1.webp b/backend/data/images/gallery/Gallery1.webp
new file mode 100644
index 0000000..cc23860
Binary files /dev/null and b/backend/data/images/gallery/Gallery1.webp differ
diff --git a/backend/data/images/gallery/Gallery2.webp b/backend/data/images/gallery/Gallery2.webp
new file mode 100644
index 0000000..75e5de4
Binary files /dev/null and b/backend/data/images/gallery/Gallery2.webp differ
diff --git a/backend/data/images/gallery/Gallery3.webp b/backend/data/images/gallery/Gallery3.webp
new file mode 100644
index 0000000..8641d8d
Binary files /dev/null and b/backend/data/images/gallery/Gallery3.webp differ
diff --git a/backend/data/images/gallery/Gallery4.webp b/backend/data/images/gallery/Gallery4.webp
new file mode 100644
index 0000000..bfb6c4e
Binary files /dev/null and b/backend/data/images/gallery/Gallery4.webp differ
diff --git a/backend/data/images/gallery/Gallery5.webp b/backend/data/images/gallery/Gallery5.webp
new file mode 100644
index 0000000..ba13919
Binary files /dev/null and b/backend/data/images/gallery/Gallery5.webp differ
diff --git a/backend/data/images/gallery/Gallery6.webp b/backend/data/images/gallery/Gallery6.webp
new file mode 100644
index 0000000..6cdb18f
Binary files /dev/null and b/backend/data/images/gallery/Gallery6.webp differ
diff --git a/backend/data/images/gallery/Gallery7.webp b/backend/data/images/gallery/Gallery7.webp
new file mode 100644
index 0000000..77f19f7
Binary files /dev/null and b/backend/data/images/gallery/Gallery7.webp differ
diff --git a/backend/data/images/gallery/Gallery8.webp b/backend/data/images/gallery/Gallery8.webp
new file mode 100644
index 0000000..7926875
Binary files /dev/null and b/backend/data/images/gallery/Gallery8.webp differ
diff --git a/backend/data/images/gallery/Gallery9.webp b/backend/data/images/gallery/Gallery9.webp
new file mode 100644
index 0000000..3d2860c
Binary files /dev/null and b/backend/data/images/gallery/Gallery9.webp differ
diff --git a/backend/package.json b/backend/package.json
index 9c21032..7f5e169 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -9,7 +9,8 @@
"start": "node dist/index.js",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
- "db:studio": "drizzle-kit studio"
+ "db:studio": "drizzle-kit studio",
+ "migrate:old-data": "tsx src/scripts/migrate-old-data.ts"
},
"dependencies": {
"@fastify/cookie": "^9.3.1",
diff --git a/backend/src/scripts/migrate-old-data.ts b/backend/src/scripts/migrate-old-data.ts
new file mode 100644
index 0000000..029a0ab
--- /dev/null
+++ b/backend/src/scripts/migrate-old-data.ts
@@ -0,0 +1,190 @@
+import { db } from '../config/database.js';
+import { events, galleryImages } from '../db/schema.js';
+import fs from 'fs';
+import path from 'path';
+import sharp from 'sharp';
+
+// Old events data
+const oldEvents = [
+ {
+ image: "/images/events/event_karaoke.jpg",
+ title: "Karaoke",
+ date: "2025-12-31", // Set as ongoing event
+ description: `Bei uns gibt es Karaoke Mi-Sa!!
+Seid ihr eine Gruppe und lieber unter euch? ..unseren 2.Stock kannst du auch mieten ;)
+Reserviere am besten gleich per Whatsapp 077 232 27 70`,
+ displayOrder: 0,
+ },
+ {
+ image: "/images/events/event_pub-quiz.jpg",
+ title: "Pub Quiz",
+ date: "2025-12-31", // Set as ongoing event
+ description: `Jeden Freitag findet unser Pub Quiz statt. Gespielt wird tischweise in 3-4 Runden.
+Jede Woche gibt es ein anderes Thema. Es geht um Ruhm und Ehre und zusätzlich werden die Sieger der Herzen durch das Publikum gekürt! <3
+Auch Einzelpersonen sind herzlich willkommen!
+*zum mitmachen minimum 1 Getränk konsumieren oder 5CHF`,
+ displayOrder: 1,
+ },
+ {
+ image: "/images/events/event_schlager-karaoke.jpeg",
+ title: "Schlager Hüttenzauber Karaoke",
+ date: "2025-11-27",
+ description: `Ab 19:00 Uhr Eintritt ist Frei! Reservieren unter 077 232 27 70`,
+ displayOrder: 2,
+ },
+ {
+ image: "/images/events/event_advents-kalender.jpeg",
+ title: "Adventskalender",
+ date: "2025-12-20",
+ description: `Jeden Tag neue Überraschungen! Check unsere Social Media Stories!`,
+ displayOrder: 3,
+ },
+ {
+ image: "/images/events/event_santa_karaoke.jpeg",
+ title: "Santa Karaoke-Party",
+ date: "2025-12-06",
+ description: `🤶🏻🎅🏻Komme als Weihnachts-Mann/-Frau und bekomme einen Shot auf's Haus!🤶🏻🎅🏻`,
+ displayOrder: 4,
+ },
+ {
+ image: "/images/events/event_ferien.jpeg",
+ title: "Weihnachtsferien",
+ date: "2025-12-21",
+ description: `Wir sind ab 02.01.2026 wieder wie gewohnt für euch da! 🍀.
Für Anfragen WA 077 232 27 70 Antwort innerhalb 48h`,
+ displayOrder: 5,
+ },
+ {
+ image: "/images/events/event_neujahrs-apero.jpeg",
+ title: "Neujahrs-Apero",
+ date: "2026-01-02",
+ description: `18:00-20:00 Uhr`,
+ displayOrder: 6,
+ },
+];
+
+// Old gallery images
+const oldGalleryImages = [
+ { src: "/images/gallery/Gallery7.png", alt: "Gallery 7" },
+ { src: "/images/gallery/Gallery8.png", alt: "Gallery 8" },
+ { src: "/images/gallery/Gallery9.png", alt: "Gallery 9" },
+ { src: "/images/gallery/Gallery6.png", alt: "Gallery 6" },
+ { src: "/images/gallery/Gallery1.png", alt: "Gallery 1" },
+ { src: "/images/gallery/Gallery2.png", alt: "Gallery 2" },
+ { src: "/images/gallery/Gallery3.png", alt: "Gallery 3" },
+ { src: "/images/gallery/Gallery4.png", alt: "Gallery 4" },
+ { src: "/images/gallery/Gallery5.png", alt: "Gallery 5" },
+];
+
+async function copyAndConvertImage(
+ sourcePath: string,
+ destDir: string,
+ filename: string
+): Promise {
+ const projectRoot = path.join(process.cwd(), '..');
+ const fullSourcePath = path.join(projectRoot, 'public', sourcePath);
+
+ // Ensure destination directory exists
+ if (!fs.existsSync(destDir)) {
+ fs.mkdirSync(destDir, { recursive: true });
+ }
+
+ const ext = path.extname(filename);
+ const baseName = path.basename(filename, ext);
+ const webpFilename = `${baseName}.webp`;
+ const destPath = path.join(destDir, webpFilename);
+
+ console.log(`Processing: ${fullSourcePath} -> ${destPath}`);
+
+ // Check if source exists
+ if (!fs.existsSync(fullSourcePath)) {
+ console.error(`Source file not found: ${fullSourcePath}`);
+ throw new Error(`Source file not found: ${fullSourcePath}`);
+ }
+
+ // Convert to webp and copy
+ await sharp(fullSourcePath)
+ .rotate() // Auto-rotate based on EXIF
+ .resize({ width: 1600, withoutEnlargement: true })
+ .webp({ quality: 85 })
+ .toFile(destPath);
+
+ return `/images/${path.relative(destDir, destPath).replace(/\\/g, '/')}`;
+}
+
+async function migrateEvents() {
+ console.log('\n=== Migrating Events ===\n');
+
+ const dataDir = process.env.GIT_WORKSPACE_DIR || path.join(process.cwd(), 'data');
+ const eventsImageDir = path.join(dataDir, 'images', 'events');
+
+ for (const event of oldEvents) {
+ try {
+ const filename = path.basename(event.image);
+ const newImageUrl = await copyAndConvertImage(
+ event.image,
+ eventsImageDir,
+ filename
+ );
+
+ const [newEvent] = await db.insert(events).values({
+ title: event.title,
+ date: event.date,
+ description: event.description,
+ imageUrl: newImageUrl,
+ displayOrder: event.displayOrder,
+ isPublished: true,
+ }).returning();
+
+ console.log(`✓ Migrated event: ${newEvent.title}`);
+ } catch (error) {
+ console.error(`✗ Failed to migrate event "${event.title}":`, error);
+ }
+ }
+}
+
+async function migrateGallery() {
+ console.log('\n=== Migrating Gallery Images ===\n');
+
+ const dataDir = process.env.GIT_WORKSPACE_DIR || path.join(process.cwd(), 'data');
+ const galleryImageDir = path.join(dataDir, 'images', 'gallery');
+
+ for (let i = 0; i < oldGalleryImages.length; i++) {
+ const img = oldGalleryImages[i];
+ try {
+ const filename = path.basename(img.src);
+ const newImageUrl = await copyAndConvertImage(
+ img.src,
+ galleryImageDir,
+ filename
+ );
+
+ const [newImage] = await db.insert(galleryImages).values({
+ imageUrl: newImageUrl,
+ altText: img.alt,
+ displayOrder: i,
+ isPublished: true,
+ }).returning();
+
+ console.log(`✓ Migrated gallery image: ${newImage.altText}`);
+ } catch (error) {
+ console.error(`✗ Failed to migrate gallery image "${img.alt}":`, error);
+ }
+ }
+}
+
+async function main() {
+ console.log('Starting migration of old data...\n');
+ console.log('Working directory:', process.cwd());
+ console.log('Data directory:', process.env.GIT_WORKSPACE_DIR || path.join(process.cwd(), 'data'));
+
+ try {
+ await migrateEvents();
+ await migrateGallery();
+ console.log('\n✓ Migration completed successfully!');
+ } catch (error) {
+ console.error('\n✗ Migration failed:', error);
+ process.exit(1);
+ }
+}
+
+main();