From bf7e38ba2db47b4a5e387ae68309c0d1edcd3959 Mon Sep 17 00:00:00 2001 From: Kenzo Date: Wed, 17 Dec 2025 20:47:38 +0100 Subject: [PATCH] feat: Add banner management feature and improve event/gallery image handling - Introduced a new "Banners" feature, enabling banner creation, management, and display across the admin panel and frontend. - Enhanced image handling for events and gallery by converting images to optimized webp format. - Added `banners` table in the database schema for storing announcements. - Integrated new `/api/banners` route in backend for banner operations. - Updated `index.astro` to include banner display component. - Added supporting UI and APIs in the admin panel for banner management. --- backend/src/db/schema.ts | 11 +++ backend/src/index.ts | 2 + backend/src/scripts/migrate-old-data.ts | 69 ++++++++++----- src/pages/admin.astro | 109 ++++++++++++++++++++++++ src/pages/index.astro | 2 + 5 files changed, 172 insertions(+), 21 deletions(-) diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts index a72ea2c..10eaf31 100644 --- a/backend/src/db/schema.ts +++ b/backend/src/db/schema.ts @@ -60,3 +60,14 @@ export const publishHistory = sqliteTable('publish_history', { commitMessage: text('commit_message'), publishedAt: integer('published_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), }); + +// Banner table (for announcements like holidays, special info) +export const banners = sqliteTable('banners', { + id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), + text: text('text').notNull(), + startDate: text('start_date').notNull(), // ISO date string + endDate: text('end_date').notNull(), // ISO date string + isActive: integer('is_active', { mode: 'boolean' }).default(true), + createdAt: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), + updatedAt: integer('updated_at', { mode: 'timestamp' }).default(sql`(unixepoch())`), +}); diff --git a/backend/src/index.ts b/backend/src/index.ts index 403cf73..f709cf2 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -16,6 +16,7 @@ import galleryRoute from './routes/gallery.js'; import contentRoute from './routes/content.js'; import settingsRoute from './routes/settings.js'; import publishRoute from './routes/publish.js'; +import bannersRoute from './routes/banners.js'; // Validate environment variables try { @@ -78,6 +79,7 @@ fastify.register(galleryRoute, { prefix: '/api' }); fastify.register(contentRoute, { prefix: '/api' }); fastify.register(settingsRoute, { prefix: '/api' }); fastify.register(publishRoute, { prefix: '/api' }); +fastify.register(bannersRoute, { prefix: '/api' }); // Health check fastify.get('/health', async () => { diff --git a/backend/src/scripts/migrate-old-data.ts b/backend/src/scripts/migrate-old-data.ts index 70f782b..029a0ab 100644 --- a/backend/src/scripts/migrate-old-data.ts +++ b/backend/src/scripts/migrate-old-data.ts @@ -9,7 +9,7 @@ const oldEvents = [ { image: "/images/events/event_karaoke.jpg", title: "Karaoke", - date: "2025-12-31", + 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`, @@ -18,7 +18,7 @@ Reserviere am besten gleich per Whatsapp 077 232 27 7 { image: "/images/events/event_pub-quiz.jpg", title: "Pub Quiz", - date: "2025-12-31", + 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!
@@ -75,43 +75,62 @@ const oldGalleryImages = [ { src: "/images/gallery/Gallery5.png", alt: "Gallery 5" }, ]; -async function processImageToBase64(sourcePath: string): Promise<{ imageData: string; mimeType: string }> { +async function copyAndConvertImage( + sourcePath: string, + destDir: string, + filename: string +): Promise { const projectRoot = path.join(process.cwd(), '..'); const fullSourcePath = path.join(projectRoot, 'public', sourcePath); - console.log(`Processing: ${fullSourcePath}`); + // 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 get buffer - const buffer = await sharp(fullSourcePath) - .rotate() - .resize({ width: 1600, withoutEnlargement: true }) - .webp({ quality: 85 }) - .toBuffer(); + // 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 { - imageData: buffer.toString('base64'), - mimeType: 'image/webp', - }; + 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 { imageData, mimeType } = await processImageToBase64(event.image); + 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, - imageData, - mimeType, + imageUrl: newImageUrl, displayOrder: event.displayOrder, isPublished: true, }).returning(); @@ -126,14 +145,21 @@ async function migrateEvents() { 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 { imageData, mimeType } = await processImageToBase64(img.src); + const filename = path.basename(img.src); + const newImageUrl = await copyAndConvertImage( + img.src, + galleryImageDir, + filename + ); const [newImage] = await db.insert(galleryImages).values({ - imageData, - mimeType, + imageUrl: newImageUrl, altText: img.alt, displayOrder: i, isPublished: true, @@ -149,6 +175,7 @@ async function migrateGallery() { 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(); @@ -160,4 +187,4 @@ async function main() { } } -main(); \ No newline at end of file +main(); diff --git a/src/pages/admin.astro b/src/pages/admin.astro index 463d3ac..91d6560 100644 --- a/src/pages/admin.astro +++ b/src/pages/admin.astro @@ -84,6 +84,24 @@ const title = 'Admin'; + +