source: Klonkt/src/middleware/site.js@ d56d471

main
Last change on this file since d56d471 was 0a7ba90, checked in by Robin Genis <roboburr@…>, 2 months ago

fix(theme): a site renders in its own palette, not the logged-in viewer's

  • middleware/site.js — loadTheme prioritised the session user's palette over the site's, so a full page load (owner logged in) used the viewer's stale user.palette while htmx-nav used the site palette → the palette appeared to change on hard refresh. There is no per-user palette UI (user.palette is vestigial migration data); the site palette now always wins, matching the htmx pcmsNav path. Theme (dark/light) stays a per-user preference.
  • Property mode set to 100644
File size: 4.9 KB
RevLine 
[7bc636b]1/**
2 * Site middleware — resolve which site this request is for.
[94a876f]3 *
4 * Resolution order (hub-modus):
5 * 1. Pad /user/:slug → die site (legacy /sites/:slug → 301 naar /user/)
6 * 2. Anders (solo, of hub-landing): de primaire/hoofd-site
7 *
[7bc636b]8 * Sets res.locals.site for all downstream handlers.
9 */
10
11import db from '../config/database.js';
[6351545]12import { getTenancy } from '../services/SettingsService.js';
[0beafed]13import { audioUrl } from '../services/AudioStreamService.js';
[cb01666]14import { audioEnabled } from '../config/features.js';
[7bc636b]15
[7881080]16/**
[834bcc3]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.
[7881080]21 */
22export function getPrimarySite() {
23 return db.prepare('SELECT * FROM sites WHERE is_primary = 1 LIMIT 1').get()
24 || db.prepare('SELECT * FROM sites ORDER BY created_at ASC LIMIT 1').get()
25 || null;
26}
27
[7bc636b]28export function resolveSite(req, res, next) {
[6351545]29 const tenancy = getTenancy();
[834bcc3]30 res.locals.tenancy = tenancy; // also available in views
[7bc636b]31
[834bcc3]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.
[6351545]34 if (tenancy === 'hub') {
[834bcc3]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/).
[bf61be7]38 const m = req.path.match(/^\/(sites|user)\/([a-zA-Z0-9_-]+)(\/.*)?$/);
[6351545]39 if (m) {
[f735304]40 if (m[1] === 'sites') {
41 return res.redirect(301, req.originalUrl.replace(/^\/sites\//, '/user/'));
42 }
[bf61be7]43 const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get(m[2]);
[6351545]44 if (site) {
45 res.locals.site = site;
[f735304]46 req.url = (m[3] || '/'); // strip /user/:slug zodat downstream de rest ziet
47 res.locals.siteUrlBase = `/user/${m[2]}`;
[6351545]48 return next();
49 }
50 }
[834bcc3]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.)
[7bc636b]54 }
55
[834bcc3]56 // Solo (or hub without a match): pin to the primary/main site.
[7881080]57 const defaultSite = getPrimarySite();
[7bc636b]58 if (defaultSite) {
59 res.locals.site = defaultSite;
60 res.locals.siteUrlBase = '';
61 }
62
63 next();
64}
65
66/**
67 * Audio tracks loader — pulls site-level tracks for the persistent player widget.
68 * Per Robin: player is separate from the footer, gated by site.enable_audio_player.
69 * Returns empty array if no site or audio is disabled — shell.ejs uses the
70 * length to decide whether to mount audio-player.js.
71 */
72export function loadAudioTracks(req, res, next) {
[cb01666]73 if (!audioEnabled()) { res.locals.audioTracks = []; return next(); } // lite-modus
[7bc636b]74 const site = res.locals.site;
75 if (!site || site.enable_audio_player === 0) {
76 res.locals.audioTracks = [];
77 return next();
78 }
79
80 try {
[834bcc3]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.
[0beafed]84 const rows = db.prepare(`
85 SELECT t.id, t.title, t.artist, t.duration, t.position, m.filename
[7bc636b]86 FROM audio_tracks t
87 LEFT JOIN media m ON m.id = t.media_id
88 WHERE t.site_id = ?
89 ORDER BY t.position ASC, t.created_at ASC
90 `).all(site.id);
[0beafed]91 res.locals.audioTracks = rows.map((r) => ({
92 id: r.id, title: r.title, artist: r.artist, duration: r.duration, position: r.position,
93 media_url: r.filename ? audioUrl(r.filename) : null,
94 }));
[7bc636b]95 } catch (e) {
96 // media table might not be queryable in some test setups — fall back gracefully
97 res.locals.audioTracks = [];
98 }
99
100 next();
101}
102
103/**
104 * Theme loader — applies user/site theme preferences.
105 */
106export function loadTheme(req, res, next) {
[c9c70cc]107 const PALETTES = ['klonkt','forest','ocean','teal','lilac','sunset','candy','amber'];
[7bc636b]108
109 const user = req.session?.user;
110 const site = res.locals.site;
111
[0a7ba90]112 // A site always renders in ITS OWN palette, regardless of who is viewing. There is
113 // no per-user palette UI (user.palette is vestigial/stale data from old migrations),
114 // and the htmx pcmsNav path (render.js) already uses the site palette only — so reading
115 // user.palette here made a full page load (owner logged in) flip to the viewer's stale
116 // palette while htmx-nav kept the site's, i.e. "palette changes on hard refresh".
117 const palette = (site && PALETTES.includes(site.palette) ? site.palette : null)
[dd7e2a2]118 || 'klonkt';
[0a7ba90]119
[7bc636b]120 res.locals.palette = palette;
121 res.locals.theme = (user && ['dark','light'].includes(user.theme)) ? user.theme : 'dark';
122
123 next();
124}
Note: See TracBrowser for help on using the repository browser.