Changeset 834bcc3 in Klonkt for src/server.js


Ignore:
Timestamp:
06/23/2026 06:14:27 PM (3 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
d774679
Parents:
bb42dfb
Message:

i18n: translate Dutch code comments to English across src/

Comments in routes/services/views/config/middleware/assets translated to
English for the public repo. A few dev-facing throw/console message strings
were Englished too. No user-facing UI strings or i18n dictionary values changed
(src/services/i18n.js untouched). Logic unchanged.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/server.js

    rbb42dfb r834bcc3  
    22 * Klonkt Beta — server bootstrap
    33 *
    4  * Persoonlijk multi-site platform — Node + SQLite + htmx.
     4 * Personal multi-site platform — Node + SQLite + htmx.
    55 * Stack: Express + better-sqlite3 + EJS + htmx + ws.
    66 */
     
    8787        "'self'",
    8888        "'unsafe-inline'",
    89         // Eigen custom-embeds (embed-player.js) laden de OFFICIELE player-API's
    90         // van deze hosts. Zonder deze whitelist blokkeert de CSP ze stil (alleen
    91         // een console-fout) en faalt de embed-speler.
     89        // Our custom embeds (embed-player.js) load the OFFICIAL player APIs
     90        // from these hosts. Without this whitelist the CSP silently blocks them
     91        // (only a console error) and the embed player fails.
    9292        "https://www.youtube.com",   // YouTube IFrame Player API (+ www-widgetapi.js)
    93         "https://s.ytimg.com",       // YouTube player-assets
     93        "https://s.ytimg.com",       // YouTube player assets
    9494        "https://w.soundcloud.com",  // SoundCloud Widget API (api.js)
    9595        "https://open.spotify.com",  // Spotify iFrame API (loader)
    96         "https://*.spotifycdn.com",  // Spotify iFrame API (echte bundle: embed-cdn.spotifycdn.com)
     96        "https://*.spotifycdn.com",  // Spotify iFrame API (real bundle: embed-cdn.spotifycdn.com)
    9797      ],
    98       // Helmet's default zet script-src-attr op 'none', wat ALLE inline event-
    99       // handlers (onchange/onclick/onsubmit) blokkeert — daardoor deed o.a. de
    100       // avatar-upload (<input onchange="this.form.submit()">) en de rol-dropdown
    101       // niets. We staan inline handlers expliciet toe, consistent met de al
    102       // toegestane inline <script> hierboven.
     98      // Helmet's default sets script-src-attr to 'none', which blocks ALL inline
     99      // event handlers (onchange/onclick/onsubmit) — causing e.g. the avatar
     100      // upload (<input onchange="this.form.submit()">) and the role dropdown to
     101      // silently do nothing. We explicitly allow inline handlers, consistent with
     102      // the already-allowed inline <script> above.
    103103      scriptSrcAttr: ["'unsafe-inline'"],
    104104      styleSrc: ["'self'", "'unsafe-inline'"],
    105       // blob: nodig voor de afbeeldings-editor (Cropper) — die toont de gekozen
    106       // foto via een URL.createObjectURL(blob:…). Zonder blob: blokkeert de CSP
    107       // de <img> stil → leeg bewerk-venster. (media-src heeft blob: al voor audio.)
     105      // blob: required for the image editor (Cropper) — it displays the chosen
     106      // photo via URL.createObjectURL(blob:…). Without blob: the CSP silently
     107      // blocks the <img> → empty edit window. (media-src already has blob: for audio.)
    108108      imgSrc: ["'self'", "data:", "https:", "blob:"],
    109109      connectSrc: ["'self'", "wss:", "ws:", "https://*.spotifycdn.com", "https://*.scdn.co"],
     
    121121        "https://embed.music.apple.com",
    122122        "https://www.youtube-nocookie.com",
    123         "https://www.youtube.com",   // YouTube IFrame API maakt soms een www.youtube.com-iframe
     123        "https://www.youtube.com",   // YouTube IFrame API sometimes creates a www.youtube.com iframe
    124124        "https://player.vimeo.com",
    125125      ],
     
    143143if (!isDev) app.set('trust proxy', 1);
    144144
    145 // Schema aanmaken/bijwerken VÓÓR iets de DB aanraakt: de session-store doet
    146 // bij constructie al een query op de `sessions`-tabel, dus bij een verse
    147 // install moeten de tabellen eerst bestaan (anders: "no such table: sessions"
    148 // → crash-loop op de allereerste boot).
     145// Create/migrate the schema BEFORE anything touches the DB: the session store
     146// queries the `sessions` table on construction, so on a fresh install the tables
     147// must exist first (otherwise: "no such table: sessions" → crash loop on first boot).
    149148initializeDatabase();
    150 startScheduler(); // release-planning: zet geplande posts live zodra publish_at bereikt is
    151 
    152 // Vangnet: garandeer dat er altijd een primaire site is (solo/hub/circle).
    153 // Idempotent — doet niets als er al een site is of nog geen beheerder.
     149startScheduler(); // release planning: publish scheduled posts when publish_at is reached
     150
     151// Safety net: guarantee that there is always a primary site (solo/hub/circle).
     152// Idempotent — does nothing if a site already exists or there is no admin yet.
    154153ensurePrimarySite();
    155154
     
    173172app.use('/assets', express.static(path.join(__dirname, 'assets'), { maxAge: isDev ? 0 : '1y' }));
    174173app.use('/media', express.static(process.env.MEDIA_PATH || './storage/media', {
    175   // Publieke media (post-covers, avatars) moet door andere Klonkt-sites in hun
    176   // CIRKEL cross-origin embedbaar zijn. Helmet zet standaard CORP=same-origin,
    177   // wat die afbeeldingen in de browser blokkeert (bestand komt wél binnen, maar
    178   // de browser weigert 'm te tonen). Voor /media dus expliciet cross-origin.
     174  // Public media (post covers, avatars) must be cross-origin embeddable by other
     175  // Klonkt sites in their CIRCLE. Helmet sets CORP=same-origin by default, which
     176  // causes the browser to block those images (the file arrives, but the browser
     177  // refuses to render it). Set cross-origin explicitly for /media.
    179178  setHeaders: (res) => res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'),
    180179}));
    181180
    182 // (Verwijderd) TWA / digital-asset-links — alleen nodig voor de APK/TWA-variant.
    183 // Klonkt is PWA-only; geen assetlinks.json meer.
    184 
    185 // Cirkels: periodieke achtergrond-sync van remote instances (no-op tenzij tenancy='circle').
     181// (Removed) TWA / digital-asset-links — only needed for the APK/TWA variant.
     182// Klonkt is PWA-only; assetlinks.json is no longer served.
     183
     184// Circles: periodic background sync of remote instances (no-op unless tenancy='circle').
    186185startCircleSyncLoop();
    187186
     
    203202})();
    204203
    205 // Cirkels-federatie: publieke, site-agnostische endpoints (/.klonkt/*).
    206 // Vóór resolveSite/theme — ze hebben geen site-context nodig.
     204// Circle federation: public, site-agnostic endpoints (/.klonkt/*).
     205// Before resolveSite/theme — they don't need a site context.
    207206app.use(federationRoutes);
    208207
     
    211210app.use(loadTheme);
    212211
    213 // Lichtgewicht CSRF-defense: weiger cross-origin state-wijzigende requests.
    214 // Same-origin forms + HTMX sturen een matchende Origin; ontbreekt Origin dan
    215 // laten we door (non-browser clients). sameSite:'lax' op de sessiecookie is de
    216 // tweede laag. (Geldt niet voor GET/HEAD/OPTIONS.)
     212// Lightweight CSRF defense: reject cross-origin state-mutating requests.
     213// Same-origin forms + HTMX send a matching Origin; missing Origin is allowed
     214// through (non-browser clients). sameSite:'lax' on the session cookie is the
     215// second layer. (Does not apply to GET/HEAD/OPTIONS.)
    217216app.use((req, res, next) => {
    218217  if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') return next();
    219218  const origin = req.get('origin');
    220   if (!origin) return next(); // geen Origin -> geen browser-CSRF-vector
     219  if (!origin) return next(); // no Origin → no browser CSRF vector
    221220  let originHost;
    222221  try { originHost = new URL(origin).host; } catch { return res.status(403).send('Ongeldige origin'); }
     
    225224});
    226225
    227 // Kijker-accounts: alles bekijken mag (incl. Beheer), niets wijzigen. Dit is de
    228 // ENIGE schrijf-blokkade — fail-closed, vóór alle route-handlers. Elke state-
    229 // wijzigende methode wordt geweigerd (de login-POST zet de sessie pas ná deze
    230 // guard, dus die valt er niet onder). I.p.v. rauwe 403-tekst tonen we een nette
    231 // pagina (of, bij HTMX, een ingeswapte melding).
     226// Viewer accounts: may view everything (including Admin), change nothing. This is
     227// the ONLY write gate — fail-closed, before all route handlers. Every state-mutating
     228// method is rejected (the login POST sets the session after this guard, so it is
     229// not affected). Instead of raw 403 text we render a clean page (or, for HTMX,
     230// a swapped-in message).
    232231app.use((req, res, next) => {
    233232  const mutating = req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS';
    234233  if (mutating && isViewer(req.session?.user)) {
    235234    if (req.headers['hx-request'] === 'true') {
    236       // htmx swapt niet op 4xx; stuur 200 + retarget zodat de melding in #pcms-main verschijnt.
     235      // htmx doesn't swap on 4xx; send 200 + retarget so the message appears in #pcms-main.
    237236      res.setHeader('HX-Retarget', '#pcms-main');
    238237      res.setHeader('HX-Reswap', 'innerHTML');
     
    277276// Feed/sitemap routes are mounted at root because they're at well-known paths
    278277app.use('/', feedRoutes);
    279 app.use('/leden', artistsRoutes); // doorzoekbare leden-directory (alleen hub; solo: next())
     278app.use('/leden', artistsRoutes); // searchable member directory (hub only; solo: next())
    280279app.get('/artiesten', (req, res) => res.redirect(301, req.originalUrl.replace(/^\/artiesten/, '/leden'))); // oude URL -> /leden
    281280app.use('/', hubRoutes); // hub-overview op '/' (solo: next() -> postsRoutes)
     
    306305
    307306  // A stable identity per site so installs don't collide (Chromium uses `id`).
    308   // NB: een id-wissel orphant bestaande PWA-installs (er is geen migratie die een
    309   // install over een id-verandering heen tilt) — wie een site al als PWA had,
    310   // moet 'm één keer opnieuw installeren. Data blijft server-side, dus niets kwijt.
     307  // NB: changing the id orphans existing PWA installs (no migration carries an
     308  // install across an id change) — anyone who already installed the site as a
     309  // PWA will need to reinstall once. Data stays server-side, so nothing is lost.
    311310  const idBase = site?.slug ? `klonkt-${site.slug}` : 'klonkt';
    312311
     
    376375  self.clients.claim();
    377376});
    378 // ALLEEN navigaties (HTML-pagina's) onderscheppen, voor een offline-fallback.
    379 // Afbeeldingen, CSS, JS en /media NIET aanraken — laat de browser die native
    380 // afhandelen. Anders kon een mislukte netwerk-fetch terugvallen op een lege
    381 // cache-match (undefined) en zo een afbeelding "kapot" maken bij een gewone
    382 // refresh (hard reload omzeilt de SW en werkte daarom wél).
     377// ONLY intercept navigations (HTML pages) for an offline fallback.
     378// Do NOT touch images, CSS, JS or /media — let the browser handle those natively.
     379// Otherwise a failed network fetch could fall back to an empty cache match
     380// (undefined) and "break" an image on a normal refresh (hard reload bypasses
     381// the SW, which is why that case worked fine).
    383382self.addEventListener('fetch', e => {
    384383  if (e.request.method !== 'GET') return;
    385   if (e.request.mode !== 'navigate') return; // alleen page-loads
     384  if (e.request.mode !== 'navigate') return; // page loads only
    386385  e.respondWith(
    387386    fetch(e.request).catch(() => caches.match('/').then(r => r || Response.error()))
     
    404403app.use((req, res) => {
    405404  res.status(404);
    406   // Nette, mobielvriendelijke 404 via de shell (viewport + nav + huisstijl).
    407   // Valt terug op kale HTML als het renderen onverhoopt faalt.
     405  // Clean, mobile-friendly 404 via the shell (viewport + nav + site theme).
     406  // Falls back to bare HTML if rendering unexpectedly fails.
    408407  try {
    409408    return renderPage(req, res, 'pages/404', {
Note: See TracChangeset for help on using the changeset viewer.