Changeset 834bcc3 in Klonkt for src/middleware


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@…>

Location:
src/middleware
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/middleware/auth.js

    rbb42dfb r834bcc3  
    3838}
    3939
    40 // Een 'kijker' mag ALLES bekijken (incl. Beheer) maar NIETS wijzigen. De
    41 // schrijf-blokkade zit in de globale guard in server.js; deze helper bepaalt
    42 // alleen "is dit een alleen-lezen account?". `readonly` is de legacy-vlag die
    43 // we nog meenemen zodat niet-gemigreerde demo-accounts geblokkeerd blijven.
     40// A 'kijker' (viewer) may VIEW everything (incl. Admin) but CHANGE nothing. The
     41// write block lives in the global guard in server.js; this helper only determines
     42// "is this a read-only account?". `readonly` is the legacy flag we still include
     43// so unmigrated demo accounts remain blocked.
    4444export function isViewer(user) {
    4545  return !!user && (user.role === 'kijker' || !!user.readonly);
     
    4949  if (!req.session?.user) return loginRedirect(req, res);
    5050  const role = req.session.user.role;
    51   // god beheert; een kijker mág het Beheer-paneel zien (alleen-lezen) — de
    52   // globale guard 403't elke write, dus dit geeft enkel kijk-toegang.
     51  // god manages; a viewer MAY see the Admin panel (read-only) — the
     52  // global guard 403s every write, so this only grants view access.
    5353  if (role !== 'god' && role !== 'kijker') {
    5454    return res.status(403).send('God role required');
     
    5757}
    5858
    59 // Mag de ingelogde user de HUIDIGE site (res.locals.site) beheren? god altijd;
    60 // anders alleen de owner van die site. Gebruikt voor site-gescopete beheerroutes
    61 // die een artiest via /user/<eigen-slug>/admin/... bereikt (res.locals.site is dan
    62 // z'n eigen site; een vreemde slug levert een andere site -> 403).
     59// Can the logged-in user manage the CURRENT site (res.locals.site)? god always;
     60// otherwise only the owner of that site. Used for site-scoped admin routes
     61// that an artist reaches via /user/<own-slug>/admin/... (res.locals.site is then
     62// their own site; a foreign slug yields a different site -> 403).
    6363export function requireSiteManager(req, res, next) {
    6464  if (!req.session?.user) return loginRedirect(req, res);
    6565  const u = req.session.user;
    66   if (u.role === 'god' || u.role === 'kijker') return next(); // kijker = alleen-lezen kijk-toegang
     66  if (u.role === 'god' || u.role === 'kijker') return next(); // viewer = read-only view access
    6767  const site = res.locals.site;
    68   // owner OF toegewezen mede-beheerder (site_members) — canAdminSite dekt beide.
     68  // owner OR assigned co-admin (site_members) — canAdminSite covers both.
    6969  if (site && PermissionsService.canAdminSite(u, site)) return next();
    7070  return res.status(403).send('Geen toegang tot deze site.');
    7171}
    7272
    73 // Idem, maar de site wordt bepaald door de :slug-parameter (bv. site-edit).
     73// Same, but the site is determined by the :slug parameter (e.g. site-edit).
    7474export function requireSiteManagerBySlug(req, res, next) {
    7575  if (!req.session?.user) return loginRedirect(req, res);
    7676  const u = req.session.user;
    77   if (u.role === 'god' || u.role === 'kijker') return next(); // kijker = alleen-lezen kijk-toegang
     77  if (u.role === 'god' || u.role === 'kijker') return next(); // viewer = read-only view access
    7878  const site = db.prepare('SELECT id, owner_id FROM sites WHERE slug = ?').get(req.params.slug);
    7979  if (site && PermissionsService.canAdminSite(u, site)) return next();
  • src/middleware/rate-limit.js

    rbb42dfb r834bcc3  
    1414import { renderPage } from './render.js';
    1515
    16 // Achter Cloudflare/Caddy kan req.ip binnenkomen als "1.2.3.4:11046" (IPv4 met
    17 // poort). express-rate-limit v7 valideert het IP en gooit anders
    18 // ERR_ERL_INVALID_IP_ADDRESS — onafgevangen async → het proces crasht (en pm2
    19 // loopt in een restart-loop). Strip een trailing IPv4-poort, val terug op de
    20 // socket, en laat IPv6 (meerdere dubbele punten) ongemoeid.
     16// Behind Cloudflare/Caddy, req.ip can arrive as "1.2.3.4:11046" (IPv4 with
     17// port). express-rate-limit v7 validates the IP and otherwise throws
     18// ERR_ERL_INVALID_IP_ADDRESS — uncaught async → the process crashes (and pm2
     19// enters a restart loop). Strip a trailing IPv4 port, fall back to the
     20// socket address, and leave IPv6 (multiple colons) untouched.
    2121function clientKey(req) {
    2222  let ip = req.ip || req.socket?.remoteAddress || '';
  • src/middleware/render.js

    rbb42dfb r834bcc3  
    2727const VIEWS_DIR = path.join(__dirname, '..', 'views');
    2828
    29 // App-versie (uit package.json) + korte commit-hash (uit .klonkt-version, door de
    30 // deploy geschreven) — getoond in de footer naast "Klonkt Beta". De hash loopt
    31 // automatisch mee bij élke deploy, dus de versie is nooit meer stale.
     29// App version (from package.json) + short commit hash (from .klonkt-version, written by
     30// the deploy script) — shown in the footer next to "Klonkt Beta". The hash is updated
     31// automatically on every deploy, so the displayed version is never stale.
    3232let APP_VERSION = '';
    3333try {
     
    3636    const sha = fs.readFileSync(path.join(__dirname, '..', '..', '.klonkt-version'), 'utf8').trim().slice(0, 7);
    3737    if (sha) APP_VERSION += ' · ' + sha;
    38   } catch { /* geen .klonkt-version (lokale dev) */ }
    39 } catch { /* geen versie beschikbaar */ }
    40 
    41 // Site-tijdzone (Beheer → Instellingen). Leeg = server-default (UTC). Wordt
    42 // toegepast op álle server-side geformatteerde datums zodat ze in de zone van
    43 // de site staan i.p.v. UTC.
     38  } catch { /* no .klonkt-version (local dev) */ }
     39} catch { /* no version available */ }
     40
     41// Site timezone (Admin → Settings). Empty = server default (UTC). Applied to
     42// all server-side formatted dates so they display in the site's timezone instead of UTC.
    4443const siteTimezone = () => getSetting('timezone') || undefined;
    4544
     
    5857  const isPartial = req.headers['hx-request'] === 'true' || req.query.partial === '1';
    5958
    60   // Voorkom dat de browser een htmx-PARTIAL (alleen #pcms-main, zónder <head>/CSS)
    61   // onder dezelfde URL cachet en bij "terug" als volledige pagina serveert →
    62   // ongestylede HTML. Vary: HX-Request scheidt partial- en full-responses in de
    63   // cache; no-store op de partial zelf laat "terug" altijd de volledige pagina
    64   // opnieuw ophalen. (Vary geldt ook voor tussenliggende caches/Cloudflare.)
     59  // Prevent the browser from caching an htmx PARTIAL (only #pcms-main, without <head>/CSS)
     60  // under the same URL and serving it as a full page on "back" → unstyled HTML.
     61  // Vary: HX-Request separates partial and full responses in the cache;
     62  // no-store on the partial forces "back" to always re-fetch the full page.
     63  // (Vary also applies to intermediate caches / Cloudflare.)
    6564  res.setHeader('Vary', 'HX-Request');
    6665  if (isPartial) res.setHeader('Cache-Control', 'no-store');
    6766
    68   // Bezit deze (niet-god) user een eigen site? Bepaalt of 'ie een "Beheer"-
    69   // ingang ziet (artiest-zelfbeheer). god ziet beheer sowieso (op rol).
     67  // Does this (non-god) user own a site? Determines whether they see an "Admin"
     68  // entry (artist self-manage). god always sees admin (by role).
    7069  let _u = req.session?.user || null;
    71   // Ververs avatar + rol uit de DB zodat een stale sessie (bv. na een
    72   // avatar-wijziging of rolwissel) zichzelf herstelt zonder opnieuw inloggen.
     70  // Refresh avatar + role from the DB so a stale session (e.g. after an
     71  // avatar change or role switch) heals itself without a new login.
    7372  if (_u && _u.id) {
    7473    const _fresh = db.prepare('SELECT avatar_url, role, lang FROM users WHERE id = ?').get(_u.id);
     
    7877    db.prepare('SELECT 1 FROM sites WHERE owner_id = ? LIMIT 1').get(_u.id));
    7978
    80   // De avatar van de SITE-EIGENAAR (niet de kijker!) — voor de Klonkt-site-kop,
    81   // zodat de artiest z'n eigen account-foto als sitefoto kan gebruiken.
     79  // The avatar of the SITE OWNER (not the viewer!) — for the Klonkt site header,
     80  // so the artist can use their own account photo as the site photo.
    8281  const _site = data.site || res.locals.site || null;
    8382  const siteOwnerAvatar = (_site && _site.owner_id)
     
    8584    : null;
    8685
    87   // Kijker-modus: alles bekijken mag, niets wijzigen. Views gebruiken canMutate
    88   // om schrijf-knoppen (posten, opslaan, verwijderen) te verbergen/uit te zetten.
     86  // Viewer mode: may view everything, change nothing. Views use canMutate
     87  // to hide/disable write buttons (post, save, delete).
    8988  const _isViewer = isViewer(_u);
    9089
    91   // Wie ziet de "Beheer"-link? god/admin, een site-eigenaar (artiest-zelfbeheer),
    92   // én een kijker (mag het Beheer alleen-lezen inzien). Eén bron van waarheid,
    93   // gespiegeld in topnav/hub-nav/profielsheet — anders raakt de link verborgen
    94   // voor wie 'm wél mag zien (kijker zag 'm eerst nergens).
     90  // Who sees the "Admin" link? god/admin, a site owner (artist self-manage),
     91  // and a viewer (may view Admin read-only). One source of truth,
     92  // mirrored in topnav/hub-nav/profile sheet — otherwise the link gets hidden
     93  // for those who should see it (viewer didn't see it anywhere before).
    9594  const _role = _u ? _u.role : null;
    9695  const canSeeBeheer = !!(_u && (_role === 'god' || _role === 'admin' || _role === 'kijker' || userOwnsSite));
    9796
    98   // Interface-taal: sessie-keuze (deze sessie) → eigen voorkeur van de ingelogde
    99   // gebruiker (users.lang) → admin-ingestelde standaard (Beheer) → env → browser → nl.
     97  // Interface language: session choice (this session) → logged-in user's own preference
     98  // (users.lang) → admin-set default (Admin → Settings) → env → browser → nl.
    10099  const _lang = resolveLang(req, {
    101100    userLang: _u && _u.lang,
     
    125124    tenancy: res.locals.tenancy || 'solo',
    126125    hubTitle: getSetting('hub_title') || '',
    127     footerNewsletter: getSetting('footer_newsletter') === '1', // nieuwsbrief-aanmelding in footer (premium)
    128     agendaEnabled: getSetting('agenda_enabled') === '1', // Agenda/evenementen tonen in de pill (premium, opt-in)
     126    footerNewsletter: getSetting('footer_newsletter') === '1', // newsletter sign-up in footer (premium)
     127    agendaEnabled: getSetting('agenda_enabled') === '1', // show agenda/events in the pill (premium, opt-in)
    129128    platforms_catalog: PLATFORMS_CATALOG,
    130129    permissions: PermissionsService,
     
    153152      // looks "unclickable". Escape any non-ASCII to \uXXXX: the header stays
    154153      // ASCII-safe and remains valid JSON that htmx parses back unchanged.
    155       // Per-site accent + palette zitten in de shell-<head> (style#pcms-site-accent
    156       // + html[data-palette]) en worden NIET mee-geswapt bij htmx-nav. Stuur ze mee
    157       // zodat de client ze bijwerkt — anders erft een artiest de kleuren van de
    158       // vorige pagina (bv. hub-paars i.p.v. eigen groen). Zelfde afleiding als shell.ejs.
     154      // Per-site accent + palette live in the shell <head> (style#pcms-site-accent
     155      // + html[data-palette]) and are NOT swapped during htmx navigation. Send them along
     156      // so the client updates them — otherwise an artist inherits the previous page's
     157      // colours (e.g. hub-purple instead of their own green). Same derivation as shell.ejs.
    159158      const _navAccent = (_site && _site.accent && /^#[0-9a-fA-F]{6}$/.test(_site.accent))
    160159        ? _site.accent : '#e8b04b';
     
    169168      }).replace(/[€-￿]/g, (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));
    170169      res.setHeader('HX-Trigger-After-Settle', triggerJson);
    171       // Site-chrome out-of-band mee-renderen, zodat de kop (topnav/profielkop/
    172       // view-switcher) bij navigatie ALTIJD bij de nieuwe pagina/artiest hoort
    173       // terwijl de audioplayer (los in document.body) blijft leven (geen
    174       // verspringen). htmx vervangt #pcms-chrome via hx-swap-oob. Niet kritisch:
    175       // faalt 't, dan blijft de oude chrome staan (geen crash).
     170      // Render the site chrome out-of-band so the header (topnav/profile header/
     171      // view-switcher) ALWAYS matches the new page/artist on navigation
     172      // while the audio player (separate in document.body) keeps playing (no
     173      // interruption). htmx replaces #pcms-chrome via hx-swap-oob. Non-critical:
     174      // if it fails, the old chrome remains (no crash).
    176175      let oobChrome = '';
    177176      try {
     
    181180          { async: false },
    182181        );
    183       } catch (e) { /* chrome-OOB overslaan */ }
     182      } catch (e) { /* skip chrome OOB */ }
    184183      return res.send(pageContent + oobChrome);
    185184    }
  • src/middleware/site.js

    rbb42dfb r834bcc3  
    1515
    1616/**
    17  * De primaire/hoofd-site — ÉÉN bron van waarheid (vervangt de "oudste site ="
    18  * hoofd"-aanname die voorheen los in resolveSite/hub/account/admin stond).
    19  * Leest de expliciete is_primary-vlag; valt terug op de oudste als die (nog)
    20  * nergens staat, zodat bestaand gedrag exact behouden blijft.
     17 * The primary/main site — ONE source of truth (replaces the "oldest site ="
     18 * main" assumption that was previously scattered across resolveSite/hub/account/admin).
     19 * Reads the explicit is_primary flag; falls back to the oldest if it isn't set
     20 * anywhere yet, so existing behaviour is preserved exactly.
    2121 */
    2222export function getPrimarySite() {
     
    2828export function resolveSite(req, res, next) {
    2929  const tenancy = getTenancy();
    30   res.locals.tenancy = tenancy; // ook beschikbaar voor views
     30  res.locals.tenancy = tenancy; // also available in views
    3131
    32   // In HUB-mode mapt /user/:slug naar een specifieke site. In SOLO-mode bestaat
    33   // er maar één site: we slaan die routing over en pinnen op de primaire site.
     32  // In HUB mode /user/:slug maps to a specific site. In SOLO mode there is only
     33  // one site: we skip that routing and pin to the primary site.
    3434  if (tenancy === 'hub') {
    35     // Een Klonkt-site is canoniek bereikbaar via /user/:slug. /sites/:slug is een
    36     // legacy-alias → 301 naar de canonieke vorm zodat er één URL-schema overblijft
    37     // (behoudt pad + querystring; raakt /admin/sites NIET, dat begint met /admin/).
     35    // A Klonkt site is canonically reachable via /user/:slug. /sites/:slug is a
     36    // legacy alias → 301 to the canonical form so one URL scheme remains
     37    // (preserves path + query string; does NOT touch /admin/sites, which starts with /admin/).
    3838    const m = req.path.match(/^\/(sites|user)\/([a-zA-Z0-9_-]+)(\/.*)?$/);
    3939    if (m) {
     
    4949      }
    5050    }
    51     // (Verwijderd: een dode "slug == hostname"-subdomein-hack. Slugs mogen geen
    52     // punten bevatten, dus die kon nooit matchen. Echte subdomein-routing zou de
    53     // subdomein-LABEL tegen de slug matchen — een aparte feature, niet dit.)
     51    // (Removed: a dead "slug == hostname" subdomain hack. Slugs may not contain
     52    // dots, so it could never match. Real subdomain routing would match the
     53    // subdomain LABEL against the slug — a separate feature, not this.)
    5454  }
    5555
    56   // Solo (of hub zonder match): pin op de primaire/hoofd-site.
     56  // Solo (or hub without a match): pin to the primary/main site.
    5757  const defaultSite = getPrimarySite();
    5858  if (defaultSite) {
     
    7979
    8080  try {
    81     // m.filename = de kale bestandsnaam; de speelbare URL is de gated stream-route
    82     // (audioUrl). De media-tabel heeft GEEN url-kolom — de oude query selecteerde
    83     // m.url en faalde dus altijd stil (lege speler). Nu bouwen we de URL uit filename.
     81    // m.filename = the bare filename; the playable URL is the gated stream route
     82    // (audioUrl). The media table has NO url column — the old query selected
     83    // m.url and always failed silently (empty player). Now we build the URL from filename.
    8484    const rows = db.prepare(`
    8585      SELECT t.id, t.title, t.artist, t.duration, t.position, m.filename
Note: See TracChangeset for help on using the changeset viewer.