117 lines
3.5 KiB
TypeScript
117 lines
3.5 KiB
TypeScript
import express from 'express';
|
|
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, questionnaireOps } from './database.js';
|
|
import authRoutes from './routes/auth.js';
|
|
import adminRoutes from './routes/admin.js';
|
|
import questionnaireRoutes from './routes/questionnaire.js';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
// Initialize database
|
|
initializeDatabase();
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 4000;
|
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(cookieParser());
|
|
|
|
// CORS for development
|
|
if (!isProduction) {
|
|
app.use(cors({
|
|
origin: 'http://localhost:5173',
|
|
credentials: true,
|
|
}));
|
|
}
|
|
|
|
// Session configuration
|
|
declare module 'express-session' {
|
|
interface SessionData {
|
|
user?: { id: number; username: string };
|
|
returnTo?: string;
|
|
}
|
|
}
|
|
|
|
app.use(session({
|
|
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: {
|
|
secure: isProduction && process.env.HTTPS === 'true',
|
|
httpOnly: true,
|
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
sameSite: isProduction ? 'strict' : 'lax',
|
|
},
|
|
}));
|
|
|
|
// API Routes
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/admin', adminRoutes);
|
|
app.use('/api/q', questionnaireRoutes);
|
|
|
|
// Serve static files in production
|
|
if (isProduction) {
|
|
const clientPath = path.join(__dirname, '../client');
|
|
app.use(express.static(clientPath));
|
|
|
|
// 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 = `
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="${pageUrl}" />
|
|
<meta property="og:title" content="${title}" />
|
|
<meta property="og:description" content="${description}" />
|
|
${questionnaire.og_image ? `<meta property="og:image" content="${questionnaire.og_image}" />` : ''}
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="${title}" />
|
|
<meta name="twitter:description" content="${description}" />
|
|
${questionnaire.og_image ? `<meta name="twitter:image" content="${questionnaire.og_image}" />` : ''}
|
|
`;
|
|
|
|
// Insert OG tags before </head>
|
|
html = html.replace('</head>', `${ogTags}</head>`);
|
|
|
|
// Update title
|
|
html = html.replace(/<title>.*?<\/title>/, `<title>${title} - Activiteiten Inventaris</title>`);
|
|
|
|
res.send(html);
|
|
});
|
|
|
|
// SPA fallback for other routes
|
|
app.get('*', (_req, res) => {
|
|
res.sendFile(path.join(clientPath, 'index.html'));
|
|
});
|
|
}
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on http://localhost:${PORT}`);
|
|
});
|
|
|