Changeset 834bcc3 in Klonkt for src/config
- Timestamp:
- 06/23/2026 06:14:27 PM (3 months ago)
- Branches:
- main
- Children:
- d774679
- Parents:
- bb42dfb
- Location:
- src/config
- Files:
-
- 4 edited
-
database.js (modified) (12 diffs)
-
features.js (modified) (1 diff)
-
google.js (modified) (5 diffs)
-
mailer.js (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
rbb42dfb r834bcc3 39 39 ensureColumn('users', 'reset_token', 'TEXT'); 40 40 ensureColumn('users', 'reset_token_expires', 'DATETIME'); 41 // Google OAuth: koppel een Google-account aan eenuser (login via Google).41 // Google OAuth: link a Google account to a user (login via Google). 42 42 ensureColumn('users', 'google_sub', 'TEXT'); 43 // Read-only/ kijk-account: kan alles bekijken maar geen wijzigingen doen.43 // Read-only/viewer account: can view everything but make no changes. 44 44 ensureColumn('users', 'readonly', 'INTEGER DEFAULT 0'); 45 // Perso onlijke interface-taal (nl|en|de). Null = volg de standaard(site/env/browser).45 // Personal interface language (nl|en|de). Null = follow the default (site/env/browser). 46 46 ensureColumn('users', 'lang', 'TEXT'); 47 47 // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed. 48 48 ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'moderate'"); 49 // Cir kels: mag deze site in cirkels van anderen verschijnen(surfacing opt-out).49 // Circles: whether this site may appear in other sites' circles (surfacing opt-out). 50 50 ensureColumn('sites', 'allow_circle', 'INTEGER DEFAULT 1'); 51 51 52 // Eén EXPLICIETE primaire/hoofd-site (= de bedrijfs-/labelsite in hub-modus,53 // de enige site in solo) i.p.v. de fragiele "oudste = hoofd"-conventie die op54 // 4 plekken gedupliceerd stond. Backfill: markeer de oudste als er nog geen55 // primaire site is, zodat bestaand gedrag exact behouden blijft.52 // One EXPLICIT primary/main site (= the company/label site in hub mode, 53 // the only site in solo) instead of the fragile "oldest = main" convention 54 // that was duplicated in 4 places. Backfill: mark the oldest if no primary 55 // site exists yet, so existing behaviour is preserved exactly. 56 56 ensureColumn('sites', 'is_primary', 'INTEGER DEFAULT 0'); 57 57 try { … … 61 61 if (oldest) db.prepare('UPDATE sites SET is_primary = 1 WHERE id = ?').run(oldest.id); 62 62 } 63 } catch (e) { /* sites -tabel nog leeg/afwezig bij verse init — ensurePrimarySite regelt 't */ }63 } catch (e) { /* sites table still empty/absent on fresh init — ensurePrimarySite handles it */ } 64 64 65 65 // v9 audit additions ————————————————————————————————————————— … … 82 82 // Per-post noindex + type 83 83 ensureColumn('posts', 'noindex', 'INTEGER DEFAULT 0'); 84 ensureColumn('posts', 'publish_at', 'DATETIME'); // release -planning (premium #3): geplandego-live84 ensureColumn('posts', 'publish_at', 'DATETIME'); // release planning (premium #3): scheduled go-live 85 85 ensureColumn('posts', 'fan_only', 'INTEGER DEFAULT 0'); // fan-only preview (premium #3) 86 86 ensureColumn('posts', 'type', "TEXT DEFAULT 'post'"); // post | foto | video | audio 87 87 88 // Statisti eken (premium-module) — kale tellers, cookievrij.89 ensureColumn('posts', 'view_count', 'INTEGER DEFAULT 0'); // weergavenper post88 // Statistics (premium module) — bare counters, cookie-free. 89 ensureColumn('posts', 'view_count', 'INTEGER DEFAULT 0'); // views per post 90 90 ensureColumn('audio_tracks', 'play_count', 'INTEGER DEFAULT 0'); // plays per track 91 ensureColumn('audio_tracks', 'downloadable', 'INTEGER DEFAULT 0'); // download- voor-email (premium #2)92 ensureColumn('audio_tracks', 'credit', 'TEXT'); // eigenaar/credit (copyright-houder)93 ensureColumn('audio_tracks', 'license', 'TEXT'); // licen tie (bv. "CC BY 4.0", "Alle rechten voorbehouden")94 ensureColumn('audio_tracks', 'link_spotify', 'TEXT'); // "open in" -links per track91 ensureColumn('audio_tracks', 'downloadable', 'INTEGER DEFAULT 0'); // download-for-email (premium #2) 92 ensureColumn('audio_tracks', 'credit', 'TEXT'); // owner/credit (copyright holder) 93 ensureColumn('audio_tracks', 'license', 'TEXT'); // license (e.g. "CC BY 4.0", "All rights reserved") 94 ensureColumn('audio_tracks', 'link_spotify', 'TEXT'); // "open in" links per track 95 95 ensureColumn('audio_tracks', 'link_youtube', 'TEXT'); 96 96 ensureColumn('audio_tracks', 'link_soundcloud', 'TEXT'); … … 123 123 `); 124 124 125 // Global e app-instellingen (key/value singleton). O.a. de tenancy-modus126 // (solo = één site, hub = bedrijfssite + /user/). Default = solo.125 // Global app settings (key/value singleton). Includes the tenancy mode 126 // (solo = one site, hub = company site + /user/). Default = solo. 127 127 db.exec(` 128 128 CREATE TABLE IF NOT EXISTS app_settings ( … … 134 134 db.prepare("INSERT OR IGNORE INTO app_settings (key, value) VALUES ('tenancy', 'solo')").run(); 135 135 136 // ── Statisti eken (premium) — cookievrij─────────────────────137 // stat_daily: p er dag per site het aantal pageviews (kale teller).138 // stat_visitor_day: per dag per site een rij per UNIEKE bezoeker-hash139 // (sha256 van IP+UA+dag-salt; de salt roteert dagelijks en wordt nooit140 // bewaard → geen persistente identifier, geen cookie, geen toestemming nodig).136 // ── Statistics (premium) — cookie-free ────────────────────── 137 // stat_daily: pageview count per day per site (bare counter). 138 // stat_visitor_day: one row per UNIQUE visitor hash per day per site 139 // (sha256 of IP+UA+day-salt; the salt rotates daily and is never stored 140 // → no persistent identifier, no cookie, no consent required). 141 141 db.exec(` 142 142 CREATE TABLE IF NOT EXISTS stat_daily ( … … 161 161 `); 162 162 163 // ── Cir kels (federatie) ─────────────────────────────────────164 // Decentral e, asymmetrische verbindingen tussen solo-instances.163 // ── Circles (federation) ──────────────────────────────────── 164 // Decentralised, asymmetric connections between solo instances. 165 165 db.exec(` 166 166 CREATE TABLE IF NOT EXISTS circle_links ( … … 200 200 `); 201 201 202 // Tags van de originele post — getoond in de cirkel(comma-separated string).202 // Tags from the original post — shown in the circle feed (comma-separated string). 203 203 ensureColumn('remote_posts', 'tags', 'TEXT'); 204 204 205 // N ieuwsbrief / mailinglijst (premium). Abonnees per site; double opt-in alsSMTP206 // er is (status 'pending' tot bevestigd), anderssingle opt-in ('confirmed').207 // 'unsub' = u itgeschreven. token = confirm/unsubscribe-sleutel (in de e-maillinks).205 // Newsletter / mailing list (premium). Subscribers per site; double opt-in when SMTP 206 // is configured (status 'pending' until confirmed), otherwise single opt-in ('confirmed'). 207 // 'unsub' = unsubscribed. token = confirm/unsubscribe key (used in email links). 208 208 db.exec(` 209 209 CREATE TABLE IF NOT EXISTS subscribers ( … … 221 221 `); 222 222 223 // Verstuurde nieuwsbrieven (historie + aantallen).223 // Sent newsletters (history + counts). 224 224 db.exec(` 225 225 CREATE TABLE IF NOT EXISTS newsletters ( … … 233 233 `); 234 234 235 // Show -agenda (premium #8): tourdata/optredens per site.235 // Show agenda (premium #8): tour dates / gigs per site. 236 236 db.exec(` 237 237 CREATE TABLE IF NOT EXISTS shows ( … … 250 250 `); 251 251 252 // Meldingen: iemand reageert op je reactie / post, of liket jepost. Snapshots253 // van naam/titel zodat de lijst goedkoop te tonen is zonderjoins.254 // NB: bewust 'user_notifications' — sommige oudere DBs hebben nog eenstale,255 // ongebruikte 'notifications'-tabel met een ander schema (geen read-kolom).252 // Notifications: someone replies to your comment / post, or likes your post. Snapshots 253 // of name/title so the list can be shown cheaply without joins. 254 // NB: deliberately named 'user_notifications' — some older DBs still have a stale, 255 // unused 'notifications' table with a different schema (no read column). 256 256 db.exec(` 257 257 CREATE TABLE IF NOT EXISTS user_notifications ( … … 270 270 `); 271 271 272 // Link-in-bio klikstatistiek (premium #6). Per (site, url) een teller; de273 // link-in-bio -pagina linkt via /links/go/:i dat de klik telt en doorstuurt.272 // Link-in-bio click statistics (premium #6). One counter per (site, url); the 273 // link-in-bio page links via /links/go/:i which counts the click and redirects. 274 274 db.exec(` 275 275 CREATE TABLE IF NOT EXISTS link_clicks ( … … 282 282 `); 283 283 284 // Likes / favo rieten: een ingelogde gebruiker kan een post liken. De set van285 // posts die een gebruiker likte = z'n favorieten (/favorieten-pagina). Eén rij286 // per (post, user); uni ek zodat liken idempotent is.284 // Likes / favourites: a logged-in user can like a post. The set of 285 // posts a user liked = their favourites (/favorieten page). One row 286 // per (post, user); unique so that liking is idempotent. 287 287 db.exec(` 288 288 CREATE TABLE IF NOT EXISTS post_likes ( -
src/config/features.js
rbb42dfb r834bcc3 1 // Feature -flags (boot-tijd, via env).1 // Feature flags (boot-time, via env). 2 2 // 3 // Lite -modus: zet KLONKT_AUDIO=off in .env om de HELE audio-feature uit te4 // schakelen — geen audio-/playlist-/download-/embed-routes, geen ffmpeg-aanroep,5 // geen speler en geen [[track]]/[[playlist]]-shortcodes. Zo draait Klonkt als6 // lichte blog/foto/EPK-site op een omgeving zónder ffmpeg/exec. Hub én Cirkels7 // blijven gewoon werken (die hangen niet van audio af).3 // Lite mode: set KLONKT_AUDIO=off in .env to disable the ENTIRE audio feature — 4 // no audio/playlist/download/embed routes, no ffmpeg calls, no player, and no 5 // [[track]]/[[playlist]] shortcodes. This lets Klonkt run as a lightweight 6 // blog/photo/EPK site on environments without ffmpeg/exec. Hub and Circles 7 // keep working (they have no audio dependency). 8 8 // 9 // Default = aan (volledige versie). Alleen de letterlijke waarde 'off' schakelt uit.9 // Default = on (full version). Only the literal value 'off' disables it. 10 10 export function audioEnabled() { 11 11 return String(process.env.KLONKT_AUDIO ?? 'on').toLowerCase() !== 'off'; -
src/config/google.js
rbb42dfb r834bcc3 1 // Google OAuth2 voor LUISTERAARS (reageren). Per-instance: de self-hoster zet2 // z'n EIGEN Google-client. Zo hangt elke site aan z'n eigen Google Cloud project3 // — geen centrale afhankelijkheid, geen gedeelde aansprakelijkheid.1 // Google OAuth2 for LISTENERS (commenting). Per-instance: each self-hoster sets 2 // their OWN Google client. This way every site is tied to its own Google Cloud 3 // project — no central dependency, no shared liability. 4 4 // 5 // Config -bron (in deze volgorde): app_settings (ingesteld via Beheer → Instellingen),6 // anders de env-vars. Niet ingesteld → geen "Login met Google"-knop; de rest van7 // de site werkt door. Google-login geeft NOOIT beheerrechten.5 // Config source (in this order): app_settings (set via Admin → Settings), 6 // otherwise env vars. Not configured → no "Login with Google" button; the rest 7 // of the site keeps working. Google login NEVER grants admin rights. 8 8 // 9 // De redirect-URI wordt afgeleid vanPUBLIC_BASE_URL (<base>/auth/google/callback),10 // o f expliciet via GOOGLE_REDIRECT_URI. Die exacte URL moet in Google Cloud staan.9 // The redirect URI is derived from PUBLIC_BASE_URL (<base>/auth/google/callback), 10 // or explicitly via GOOGLE_REDIRECT_URI. That exact URL must be listed in Google Cloud. 11 11 12 12 import { getSetting } from '../services/SettingsService.js'; … … 16 16 const USERINFO_URL = 'https://openidconnect.googleapis.com/v1/userinfo'; 17 17 18 // Dynamisch lezen (UI-wijziging werkt zonder herstart). app_settings wint, env = fallback.18 // Read dynamically (UI changes take effect without a restart). app_settings wins, env = fallback. 19 19 function clientId() { 20 20 return getSetting('google_client_id', '') || process.env.GOOGLE_CLIENT_ID || ''; … … 29 29 } 30 30 31 export function currentClientId() { return clientId(); } // n iet-geheim, voor het formulier31 export function currentClientId() { return clientId(); } // not secret, used for the settings form 32 32 export function clientSecretSet() { return !!clientSecret(); } 33 33 export function googleConfigured() { … … 61 61 body, 62 62 }); 63 if (!r.ok) throw new Error(`Google token -exchange faalde: ${r.status}`);63 if (!r.ok) throw new Error(`Google token exchange failed: ${r.status}`); 64 64 return r.json(); // { access_token, id_token, ... } 65 65 } … … 68 68 export async function fetchUserinfo(accessToken) { 69 69 const r = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${accessToken}` } }); 70 if (!r.ok) throw new Error(`Google userinfo fa alde: ${r.status}`);70 if (!r.ok) throw new Error(`Google userinfo failed: ${r.status}`); 71 71 return r.json(); 72 72 } -
src/config/mailer.js
rbb42dfb r834bcc3 1 // E-mail versturen (wachtwoord-reset, nieuwsbrief, notify). Optioneel: alleen actief2 // als SMTP is ingesteld — via Beheer → Instellingen (app_settings) OF env-vars.1 // Send email (password reset, newsletter, notify). Optional: only active 2 // when SMTP is configured — via Admin → Settings (app_settings) OR env vars. 3 3 // 4 // Config -bron (in deze volgorde): app_settings (ingesteld in de UI), andersenv:4 // Config source (in this order): app_settings (set via the UI), otherwise env: 5 5 // SMTP_HOST, SMTP_PORT (default 587), SMTP_USER, SMTP_PASS, SMTP_FROM (default = USER) 6 // N iet ingesteld → versturen valt terug op CLI (reset-admin) / wordt overgeslagen.6 // Not configured → sending falls back to CLI (reset-admin) / is skipped. 7 7 8 8 import nodemailer from 'nodemailer'; … … 23 23 } 24 24 25 // Status voor de UI (zonder het wachtwoord te lekken).25 // Status for the UI (without leaking the password). 26 26 export function mailerStatus() { 27 27 const c = cfg(); … … 33 33 from: c.from, 34 34 passSet: !!c.pass, 35 // bron: handig om te tonen dat env nog actief is35 // source: useful to show that env vars are still active 36 36 fromEnv: !getSetting('smtp_host', '') && !!process.env.SMTP_HOST, 37 37 }; 38 38 } 39 39 40 // Transport cachen, maar herbouwen zodra de config wijzigt (UI-edit zonder herstart).40 // Cache the transport, but rebuild it whenever the config changes (UI edit without restart). 41 41 let _transport = null, _key = null; 42 42 function transport() { … … 47 47 host: c.host, 48 48 port: c.port, 49 secure: c.port === 465, // 465 = implici eteTLS; 587 = STARTTLS49 secure: c.port === 465, // 465 = implicit TLS; 587 = STARTTLS 50 50 auth: { user: c.user, pass: c.pass }, 51 51 }); … … 56 56 57 57 export async function sendMail({ to, subject, text, html }) { 58 if (!mailerConfigured()) throw new Error('SMTP n iet geconfigureerd');58 if (!mailerConfigured()) throw new Error('SMTP not configured'); 59 59 const c = cfg(); 60 60 return transport().sendMail({ from: c.from, to, subject, text, html });
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)