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.
This commit is contained in:
@ -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())`),
|
||||
});
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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!! <br>
|
||||
Seid ihr eine Gruppe und lieber unter euch? ..unseren 2.Stock kannst du auch mieten ;) <br>
|
||||
Reserviere am besten gleich per Whatsapp <a href="tel:+41772322770">077 232 27 70</a>`,
|
||||
@ -18,7 +18,7 @@ Reserviere am besten gleich per Whatsapp <a href="tel:+41772322770">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 <b>Pub Quiz</b> statt. Gespielt wird tischweise in 3-4 Runden. <br>
|
||||
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 <br>
|
||||
Auch Einzelpersonen sind herzlich willkommen! <br>
|
||||
@ -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<string> {
|
||||
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();
|
||||
main();
|
||||
|
||||
Reference in New Issue
Block a user