/** * Klonkt Beta — server bootstrap * * Persoonlijk multi-site platform — Node + SQLite + htmx. * 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 { startScheduler } from './services/Scheduler.js'; import { SqliteSessionStore } from './services/SqliteSessionStore.js'; import { ensurePrimarySite } from './services/ensurePrimarySite.js'; import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js'; import { isViewer } from './middleware/auth.js'; import { renderPage } from './middleware/render.js'; import { audioEnabled } from './config/features.js'; import authRoutes from './routes/auth.js'; import accountRoutes from './routes/account.js'; import notificationsRoutes from './routes/notifications.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 adminSettingsRoutes from './routes/admin-settings.js'; import adminSeoRoutes from './routes/admin-seo.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 hubRoutes from './routes/hub.js'; import artistsRoutes from './routes/artists.js'; import postsRoutes from './routes/posts.js'; import langRoutes from './routes/lang.js'; import federationRoutes from './routes/federation.js'; import { startCircleSyncLoop } from './services/CircleService.js'; import adminCircleRoutes from './routes/admin-circle.js'; import adminUpdatesRoutes from './routes/admin-updates.js'; import adminPatreonRoutes from './routes/admin-patreon.js'; import adminStatsRoutes from './routes/admin-stats.js'; import circleRoutes from './routes/circle.js'; import epkRoutes from './routes/epk.js'; import newsletterRoutes from './routes/newsletter.js'; import adminNewsletterRoutes from './routes/admin-newsletter.js'; import downloadRoutes from './routes/download.js'; import linkbioRoutes from './routes/linkbio.js'; import embedRoutes from './routes/embed.js'; import showsRoutes from './routes/shows.js'; import adminShowsRoutes from './routes/admin-shows.js'; import adminEpkRoutes from './routes/admin-epk.js'; import changelogRoutes from './routes/changelog.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'", // Eigen custom-embeds (embed-player.js) laden de OFFICIELE player-API's // van deze hosts. Zonder deze whitelist blokkeert de CSP ze stil (alleen // een console-fout) en faalt de embed-speler. "https://www.youtube.com", // YouTube IFrame Player API (+ www-widgetapi.js) "https://s.ytimg.com", // YouTube player-assets "https://w.soundcloud.com", // SoundCloud Widget API (api.js) "https://open.spotify.com", // Spotify iFrame API (loader) "https://*.spotifycdn.com", // Spotify iFrame API (echte bundle: embed-cdn.spotifycdn.com) ], // Helmet's default zet script-src-attr op 'none', wat ALLE inline event- // handlers (onchange/onclick/onsubmit) blokkeert — daardoor deed o.a. de // avatar-upload () en de rol-dropdown // niets. We staan inline handlers expliciet toe, consistent met de al // toegestane inline