diff --git a/server/database.ts b/server/database.ts index 36cebb5..548a269 100644 --- a/server/database.ts +++ b/server/database.ts @@ -34,6 +34,7 @@ export interface Questionnaire { slug: string; title: string; description: string | null; + og_image: string | null; is_private: boolean; created_by: number; created_at: string; @@ -121,6 +122,13 @@ export function initializeDatabase(): void { // Column already exists, ignore } + // Migration: Add og_image column if it doesn't exist + try { + db.exec(`ALTER TABLE questionnaires ADD COLUMN og_image TEXT`); + } catch (e) { + // Column already exists, ignore + } + // Participants table db.exec(` CREATE TABLE IF NOT EXISTS participants ( @@ -251,10 +259,10 @@ export const userOps = { // Questionnaire operations export const questionnaireOps = { - create: (uuid: string, slug: string, title: string, description: string | null, isPrivate: boolean, createdBy: number): number => { + create: (uuid: string, slug: string, title: string, description: string | null, ogImage: string | null, isPrivate: boolean, createdBy: number): number => { const result = db.prepare( - 'INSERT INTO questionnaires (uuid, slug, title, description, is_private, created_by) VALUES (?, ?, ?, ?, ?, ?)' - ).run(uuid, slug, title, description, isPrivate ? 1 : 0, createdBy); + 'INSERT INTO questionnaires (uuid, slug, title, description, og_image, is_private, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)' + ).run(uuid, slug, title, description, ogImage, isPrivate ? 1 : 0, createdBy); return result.lastInsertRowid as number; }, @@ -280,8 +288,8 @@ export const questionnaireOps = { `).all() as Questionnaire[]; }, - update: (id: number, slug: string, title: string, description: string | null, isPrivate: boolean): void => { - db.prepare('UPDATE questionnaires SET slug = ?, title = ?, description = ?, is_private = ? WHERE id = ?').run(slug, title, description, isPrivate ? 1 : 0, id); + update: (id: number, slug: string, title: string, description: string | null, ogImage: string | null, isPrivate: boolean): void => { + db.prepare('UPDATE questionnaires SET slug = ?, title = ?, description = ?, og_image = ?, is_private = ? WHERE id = ?').run(slug, title, description, ogImage, isPrivate ? 1 : 0, id); }, delete: (id: number): void => { diff --git a/server/index.ts b/server/index.ts index e4a648e..e34ea6f 100644 --- a/server/index.ts +++ b/server/index.ts @@ -3,9 +3,10 @@ import session from 'express-session'; import cookieParser from 'cookie-parser'; import cors from 'cors'; import path from 'path'; +import fs from 'fs'; import { fileURLToPath } from 'url'; -import { initializeDatabase } from './database.js'; +import { initializeDatabase, questionnaireOps } from './database.js'; import authRoutes from './routes/auth.js'; import adminRoutes from './routes/admin.js'; import questionnaireRoutes from './routes/questionnaire.js'; @@ -63,7 +64,47 @@ if (isProduction) { const clientPath = path.join(__dirname, '../client'); app.use(express.static(clientPath)); - // SPA fallback + // Dynamic OG meta tags for questionnaire pages + app.get('/q/:slug', (req, res) => { + const questionnaire = questionnaireOps.findBySlug(req.params.slug); + const indexPath = path.join(clientPath, 'index.html'); + + if (!questionnaire) { + res.sendFile(indexPath); + return; + } + + // Read the index.html template + let html = fs.readFileSync(indexPath, 'utf-8'); + + // Build meta tags + const baseUrl = `${req.protocol}://${req.get('host')}`; + const pageUrl = `${baseUrl}/q/${questionnaire.slug}`; + const title = questionnaire.title; + const description = questionnaire.description || 'Activiteiten Inventaris - Voeg activiteiten toe en stem!'; + + const ogTags = ` + + + + + ${questionnaire.og_image ? `` : ''} + + + + ${questionnaire.og_image ? `` : ''} + `; + + // Insert OG tags before + html = html.replace('', `${ogTags}`); + + // Update title + html = html.replace(/
+ URL naar een afbeelding die getoond wordt bij het delen van de link op WhatsApp, Facebook, etc. + Aanbevolen formaat: 1200x630 pixels. +
+ {ogImage && ( +Preview:
+