/**
* 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 { SqliteSessionStore } from './services/SqliteSessionStore.js';
import { ensurePrimarySite } from './services/ensurePrimarySite.js';
import PrutterService from './services/PrutterService.js';
import { WebSocketServer } from 'ws';
import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js';
import { isViewer } from './middleware/auth.js';
import { renderPage } from './middleware/render.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 adminSettingsRoutes from './routes/admin-settings.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 hubRoutes from './routes/hub.js';
import artistsRoutes from './routes/artists.js';
import postsRoutes from './routes/posts.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';
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
],
// 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