diff --git a/backend/Dockerfile b/backend/Dockerfile index aafe7b1..15f438c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -34,6 +34,10 @@ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/src/db/migrations ./dist/db/migrations +# Copy migration script and migrated images +COPY --from=builder /app/migrate-production.js ./migrate-production.js +COPY --from=builder /app/data/images ./data/images + # Create directories RUN mkdir -p /app/workspace /app/data diff --git a/backend/migrate-production.js b/backend/migrate-production.js new file mode 100644 index 0000000..2a1729a --- /dev/null +++ b/backend/migrate-production.js @@ -0,0 +1,183 @@ +// Production migration script - can be run directly with node +import Database from 'better-sqlite3'; +import { drizzle } from 'drizzle-orm/better-sqlite3'; +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; +import { sql } from 'drizzle-orm'; +import fs from 'fs'; +import path from 'path'; +import sharp from 'sharp'; + +// Database schema +const events = sqliteTable('events', { + id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + title: text('title').notNull(), + date: text('date').notNull(), + description: text('description').notNull(), + imageUrl: text('image_url').notNull(), + displayOrder: integer('display_order').notNull(), + isPublished: integer('is_published', { mode: 'boolean' }).default(true), + createdAt: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), + updatedAt: integer('updated_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), +}); + +const galleryImages = sqliteTable('gallery_images', { + id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + imageUrl: text('image_url').notNull(), + altText: text('alt_text').notNull(), + displayOrder: integer('display_order').notNull(), + isPublished: integer('is_published', { mode: 'boolean' }).default(true), + createdAt: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), +}); + +// Old events data +const oldEvents = [ + { + title: "Karaoke", + date: "2025-12-31", + 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`, + imageUrl: "/images/events/event_karaoke.webp", + displayOrder: 0, + }, + { + title: "Pub Quiz", + date: "2025-12-31", + 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, + }, + { + title: "Schlager Hüttenzauber Karaoke", + date: "2025-11-27", + description: `Ab 19:00 Uhr Eintritt ist Frei! Reservieren unter 077 232 27 70`, + imageUrl: "/images/events/event_schlager-karaoke.webp", + displayOrder: 2, + }, + { + title: "Adventskalender", + date: "2025-12-20", + description: `Jeden Tag neue Überraschungen! Check unsere Social Media Stories!`, + imageUrl: "/images/events/event_advents-kalender.webp", + displayOrder: 3, + }, + { + title: "Santa Karaoke-Party", + date: "2025-12-06", + description: `🤶🏻🎅🏻Komme als Weihnachts-Mann/-Frau und bekomme einen Shot auf's Haus!🤶🏻🎅🏻`, + imageUrl: "/images/events/event_santa_karaoke.webp", + displayOrder: 4, + }, + { + 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`, + imageUrl: "/images/events/event_ferien.webp", + displayOrder: 5, + }, + { + title: "Neujahrs-Apero", + date: "2026-01-02", + description: `18:00-20:00 Uhr`, + imageUrl: "/images/events/event_neujahrs-apero.webp", + displayOrder: 6, + }, +]; + +// Old gallery images +const oldGalleryImages = [ + { imageUrl: "/images/gallery/Gallery7.webp", alt: "Gallery 7", order: 0 }, + { imageUrl: "/images/gallery/Gallery8.webp", alt: "Gallery 8", order: 1 }, + { imageUrl: "/images/gallery/Gallery9.webp", alt: "Gallery 9", order: 2 }, + { imageUrl: "/images/gallery/Gallery6.webp", alt: "Gallery 6", order: 3 }, + { imageUrl: "/images/gallery/Gallery1.webp", alt: "Gallery 1", order: 4 }, + { imageUrl: "/images/gallery/Gallery2.webp", alt: "Gallery 2", order: 5 }, + { imageUrl: "/images/gallery/Gallery3.webp", alt: "Gallery 3", order: 6 }, + { imageUrl: "/images/gallery/Gallery4.webp", alt: "Gallery 4", order: 7 }, + { imageUrl: "/images/gallery/Gallery5.webp", alt: "Gallery 5", order: 8 }, +]; + +async function main() { + console.log('=== Production Migration Script ===\n'); + + const dbPath = process.env.DATABASE_PATH || '/app/data/gallus_cms.db'; + console.log('Database path:', dbPath); + + // Check if database exists + if (!fs.existsSync(dbPath)) { + console.error('ERROR: Database not found at:', dbPath); + console.error('Please ensure the backend has been started at least once to create the database.'); + process.exit(1); + } + + // Check if images exist + const dataDir = process.env.GIT_WORKSPACE_DIR || '/app/data'; + const eventsDir = path.join(dataDir, 'images', 'events'); + const galleryDir = path.join(dataDir, 'images', 'gallery'); + + console.log('Events images directory:', eventsDir); + console.log('Gallery images directory:', galleryDir); + + if (!fs.existsSync(eventsDir)) { + console.error('ERROR: Events images directory not found:', eventsDir); + process.exit(1); + } + + if (!fs.existsSync(galleryDir)) { + console.error('ERROR: Gallery images directory not found:', galleryDir); + process.exit(1); + } + + // List available images + console.log('\nAvailable event images:', fs.readdirSync(eventsDir)); + console.log('Available gallery images:', fs.readdirSync(galleryDir)); + + // Connect to database + const sqlite = new Database(dbPath); + const db = drizzle(sqlite); + + console.log('\n=== Migrating Events ===\n'); + + for (const event of oldEvents) { + try { + const [newEvent] = await db.insert(events).values({ + title: event.title, + date: event.date, + description: event.description, + imageUrl: event.imageUrl, + displayOrder: event.displayOrder, + isPublished: true, + }).returning(); + + console.log(`✓ Migrated event: ${newEvent.title}`); + } catch (error) { + console.error(`✗ Failed to migrate event "${event.title}":`, error.message); + } + } + + console.log('\n=== Migrating Gallery Images ===\n'); + + for (const img of oldGalleryImages) { + try { + const [newImage] = await db.insert(galleryImages).values({ + imageUrl: img.imageUrl, + altText: img.alt, + displayOrder: img.order, + isPublished: true, + }).returning(); + + console.log(`✓ Migrated gallery image: ${newImage.altText}`); + } catch (error) { + console.error(`✗ Failed to migrate gallery image "${img.alt}":`, error.message); + } + } + + sqlite.close(); + + console.log('\n✓ Migration completed successfully!'); + console.log('\nYou can verify the migration by visiting:'); + console.log('- Frontend: https://gallus-pub.ch/'); + console.log('- Admin: https://gallus-pub.ch/admin'); +} + +main().catch(error => { + console.error('\n✗ Migration failed:', error); + process.exit(1); +});