/** * PrutFolio v1 — server bootstrap * * Persoonlijk multi-site platform forked van PrutCMS v9 (PHP, file-based). * Stack: Express + better-sqlite3 + EJS + htmx + ws. */ import 'dotenv/config'; import express from 'express'; import helmet from 'helmet'; import session from 'express-session'; import bodyParser from 'body-parser'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import http from 'http'; import db, { initializeDatabase } from './config/database.js'; import { SqliteSessionStore } from './services/SqliteSessionStore.js'; import PrutterService from './services/PrutterService.js'; import { WebSocketServer } from 'ws'; import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js'; import authRoutes from './routes/auth.js'; import accountRoutes from './routes/account.js'; import adminRoutes from './routes/admin.js'; import adminAudioRoutes from './routes/admin-audio.js'; import adminPlaylistsRoutes from './routes/admin-playlists.js'; import adminSitesRoutes from './routes/admin-sites.js'; import adminUsersRoutes from './routes/admin-users.js'; import adminCommentsRoutes from './routes/admin-comments.js'; import prutterRoutes from './routes/prutter.js'; import audioRoutes from './routes/audio.js'; import searchRoutes from './routes/search.js'; import commentsRoutes from './routes/comments.js'; import tagsRoutes from './routes/tags.js'; import typesRoutes from './routes/types.js'; import usersRoutes from './routes/users.js'; import feedRoutes from './routes/feed.js'; import postsRoutes from './routes/posts.js'; if (!process.env.SESSION_SECRET) { console.error('❌ FATAL: SESSION_SECRET is required'); process.exit(1); } if (process.env.NODE_ENV === 'production' && process.env.SESSION_SECRET.length < 32) { console.error('❌ FATAL: SESSION_SECRET too weak for production'); process.exit(1); } const __dirname = path.dirname(fileURLToPath(import.meta.url)); const PORT = process.env.PORT || 3000; const isDev = process.env.NODE_ENV !== 'production'; const app = express(); const server = http.createServer(app); app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "wss:", "ws:"], // blob: is required for the audio player — it fetch()es track bytes and // plays from a blob: object URL (Spotify-style). Without blob: here the // CSP silently blocks