- Timestamp:
- 06/23/2026 02:24:46 PM (3 months ago)
- Branches:
- main
- Children:
- cb01666
- Parents:
- 24cdcc6
- Location:
- src
- Files:
-
- 4 deleted
- 9 edited
-
config/database.js (modified) (1 diff)
-
routes/admin-sites.js (modified) (4 diffs)
-
routes/posts.js (modified) (1 diff)
-
routes/prutter.js (deleted)
-
server.js (modified) (6 diffs)
-
services/PrutterService.js (deleted)
-
views/pages/admin-site-edit.ejs (modified) (1 diff)
-
views/pages/prutter-conversation.ejs (deleted)
-
views/pages/prutter-inbox.ejs (deleted)
-
views/pages/user.ejs (modified) (1 diff)
-
views/partials/bottom-tab.ejs (modified) (3 diffs)
-
views/partials/topnav.ejs (modified) (2 diffs)
-
views/shell.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r24cdcc6 r8f2f97c 47 47 // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed. 48 48 ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'moderate'"); 49 // Per-site Prutter toggle: when off, DM endpoints/UI are hidden for that site.50 ensureColumn('sites', 'enable_prutter', 'INTEGER DEFAULT 1');51 49 // Cirkels: mag deze site in cirkels van anderen verschijnen (surfacing opt-out). 52 50 ensureColumn('sites', 'allow_circle', 'INTEGER DEFAULT 1'); -
src/routes/admin-sites.js
r24cdcc6 r8f2f97c 98 98 const RESERVED_SITE_SLUGS = new Set([ 99 99 'auth', 'admin', 'login', 'register', 'logout', 'archive', 'search', 100 'account', 'sites', 'comments', 'posts', 'media', 'audio', 'prutter',100 'account', 'sites', 'comments', 'posts', 'media', 'audio', 101 101 'forum', 'tag', 'user', 'users', 'artiesten', 'leden', 'feed.xml', 'atom.xml', 'sitemap.xml', 102 102 'manifest.webmanifest', 'sw.js', 'favicon.ico', 'favicon.svg', 'assets', … … 119 119 require_login_to_comment: 1, 120 120 enable_audio_player: 1, 121 enable_prutter: 1,122 121 comments_moderation_mode: 'moderate', 123 122 feed_view_default: 'grid', … … 300 299 profile_links = ?, 301 300 is_public = ?, robots_index = ?, require_login_to_comment = ?, 302 enable_audio_player = ?, enable_prutter = ?,301 enable_audio_player = ?, 303 302 comments_moderation_mode = ?, 304 303 feed_view_default = ?, feed_view_switch = ?, … … 322 321 f.require_login_to_comment ? 1 : 0, 323 322 f.enable_audio_player ? 1 : 0, 324 f.enable_prutter ? 1 : 0,325 323 moderationMode, 326 324 feedViewDef, -
src/routes/posts.js
r24cdcc6 r8f2f97c 81 81 'auth', 'admin', 'login', 'register', 'logout', 82 82 'archive', 'search', 'account', 'sites', 'comments', 83 'posts', 'media', 'audio', ' prutter', 'forum',83 'posts', 'media', 'audio', 'forum', 84 84 'tag', 'type', 'user', 'users', 'artiesten', 'leden', 'favorieten', 'feed.xml', 'atom.xml', 'sitemap.xml', 85 85 'manifest.webmanifest', 'sw.js', 'favicon.ico', 'favicon.svg', 'assets', -
src/server.js
r24cdcc6 r8f2f97c 19 19 import { SqliteSessionStore } from './services/SqliteSessionStore.js'; 20 20 import { ensurePrimarySite } from './services/ensurePrimarySite.js'; 21 import PrutterService from './services/PrutterService.js';22 import { WebSocketServer } from 'ws';23 24 21 import { resolveSite, loadAudioTracks, loadTheme } from './middleware/site.js'; 25 22 import { isViewer } from './middleware/auth.js'; … … 36 33 import adminSettingsRoutes from './routes/admin-settings.js'; 37 34 import adminSeoRoutes from './routes/admin-seo.js'; 38 import prutterRoutes from './routes/prutter.js';39 35 import audioRoutes from './routes/audio.js'; 40 36 import searchRoutes from './routes/search.js'; … … 206 202 })(); 207 203 208 // Singleton PrutterService — routes get it via req.app.locals.prutter.209 const prutter = new PrutterService(db);210 app.locals.prutter = prutter;211 212 204 // Cirkels-federatie: publieke, site-agnostische endpoints (/.klonkt/*). 213 205 // Vóór resolveSite/theme — ze hebben geen site-context nodig. … … 274 266 app.use('/admin/epk', adminEpkRoutes); 275 267 app.use('/admin', adminRoutes); 276 app.use('/prutter', prutterRoutes);277 268 app.use('/audio', audioRoutes); 278 269 app.use('/search', searchRoutes); … … 422 413 }); 423 414 424 // ==================== WebSocket: Prutter real-time ====================425 // Authenticate via the existing session cookie. We reuse sessionMiddleware426 // during the HTTP upgrade so req.session is populated; if no user, abort.427 const wss = new WebSocketServer({ noServer: true });428 429 server.on('upgrade', (req, socket, head) => {430 if (req.url !== '/ws/prutter') {431 socket.destroy();432 return;433 }434 // Run session middleware on the upgrade request.435 // (Express's middleware accepts (req, res, next); we pass a stub res.)436 const stubRes = { setHeader: () => {}, getHeader: () => undefined, on: () => {}, end: () => {} };437 sessionMiddleware(req, stubRes, () => {438 if (!req.session?.user) {439 socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');440 socket.destroy();441 return;442 }443 // Kijker-accounts zijn alleen-lezen: weiger de WS-upgrade. De HTTP-guard444 // dekt geen WS, dus dit is de plek om schrijven via een (toekomstige)445 // message-handler te voorkomen.446 if (isViewer(req.session.user)) {447 socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');448 socket.destroy();449 return;450 }451 wss.handleUpgrade(req, socket, head, (ws) => {452 ws.userId = req.session.user.id;453 wss.emit('connection', ws, req);454 });455 });456 });457 458 wss.on('connection', (ws) => {459 prutter.addConnection(ws.userId, ws);460 ws.on('close', () => prutter.removeConnection(ws.userId, ws));461 ws.on('error', () => prutter.removeConnection(ws.userId, ws));462 // Optional: ping every 30s to keep connections alive through proxies463 ws.isAlive = true;464 ws.on('pong', () => { ws.isAlive = true; });465 });466 const wsPing = setInterval(() => {467 for (const ws of wss.clients) {468 if (ws.isAlive === false) { ws.terminate(); continue; }469 ws.isAlive = false;470 try { ws.ping(); } catch {}471 }472 }, 30000);473 if (wsPing.unref) wsPing.unref();474 475 415 server.listen(PORT, () => { 476 416 console.log(''); … … 483 423 console.log(` ✓ Auth: wachtwoord (beheer) + Google (luisteraars) / logout`); 484 424 console.log(` ✓ Posts: create / edit / view / archive`); 485 console.log(` ✓ Realtime: WebSocket server ready (Prutter)`);486 425 console.log(''); 487 426 console.log(` Mode: ${isDev ? 'development' : 'PRODUCTION'}`); -
src/views/pages/admin-site-edit.ejs
r24cdcc6 r8f2f97c 163 163 <span><%= t('asite.enable_audio') %></span> 164 164 </label> 165 <%# Prutter is een Hub-only premium-functie — toggle alleen tonen in Hub-modus. %>166 <% if (typeof tenancy !== 'undefined' && tenancy === 'hub') { %>167 <label class="cb">168 <input type="checkbox" name="enable_prutter" value="1" <%= site.enable_prutter ? 'checked' : '' %>>169 <span><%= t('asite.enable_prutter') %> <small class="form-hint-inline"><%= t('asite.enable_prutter_hint') %></small></span>170 </label>171 <% } %>172 165 </fieldset> 173 166 -
src/views/pages/user.ejs
r24cdcc6 r8f2f97c 27 27 <% } %> 28 28 </p> 29 <% if (user && user.id !== author.id && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked)) { %>30 <p class="user-actions">31 <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 <%= t('pusr.send_dm') %></a>32 </p>33 <% } %>34 29 </div> 35 30 </header> -
src/views/partials/bottom-tab.ejs
r24cdcc6 r8f2f97c 13 13 const _canPost = !!(user && (typeof canMutate === 'undefined' || canMutate) 14 14 && permissions && permissions.canCreatePost && permissions.canCreatePost(user, site)); 15 const _hasPrutter = !!(user && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked));16 15 const _ownProfile = user ? ('/users/' + user.username) : null; 17 16 const _isHub = (typeof tenancy !== 'undefined' && tenancy === 'hub'); … … 32 31 else if (_p.indexOf('/search') === 0 || _p.indexOf('/tag/') === 0 || _p.indexOf('/type/') === 0) _active = 'search'; 33 32 else if (_p === '/posts/new' || /^\/posts\/[^/]+\/edit$/.test(_p)) _active = 'create'; 34 else if (_p.indexOf('/prutter') === 0) _active = 'prutter';35 33 else if (_p.indexOf('/account') === 0 || (_ownProfile && _p.indexOf(_ownProfile) === 0)) _active = 'profile'; 36 34 else if (_p.indexOf('/auth/login') === 0) _active = 'login'; … … 80 78 <% } %> 81 79 82 <!-- Prutter (DMs) -->83 <% if (_hasPrutter) { %>84 <a class="bottom-tab-item<%= _active === 'prutter' ? ' is-active' : '' %>"85 href="<%= _siteUrlBase %>/prutter"86 aria-label="Prutter berichten" <%= _active === 'prutter' ? 'aria-current="page"' : '' %>>87 <svg class="bottom-tab-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">88 <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>89 </svg>90 <span class="bottom-tab-label">Prutter</span>91 <% if (typeof unreadDmCount !== 'undefined' && unreadDmCount > 0) { %>92 <span class="bottom-tab-badge" aria-label="<%= unreadDmCount %> ongelezen"><%= unreadDmCount > 99 ? '99+' : unreadDmCount %></span>93 <% } %>94 </a>95 <% } %>96 80 97 81 <!-- Profiel / Inloggen --> -
src/views/partials/topnav.ejs
r24cdcc6 r8f2f97c 15 15 const _isAdmin = typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0; 16 16 const _hasSearch = !site || site.show_search === undefined || site.show_search; 17 const _hasPrutter = user && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked);18 17 const _canPost = user && (typeof canMutate === 'undefined' || canMutate) 19 18 && permissions && permissions.canCreatePost && permissions.canCreatePost(user, site); … … 98 97 </div> 99 98 </details> 100 <% } %>101 102 <% if (_hasPrutter) { %>103 <a class="nav-btn" href="<%= _siteUrlBase %>/prutter" aria-label="Prutter (DMs)" title="Prutter">104 <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>105 </a>106 99 <% } %> 107 100 -
src/views/shell.ejs
r24cdcc6 r8f2f97c 33 33 // Listing pages (search/tag/type/archive) shouldn't be indexed (dupe content) 34 34 if (currentPath) { 35 if (/^\/(?:search|tag|type|archive|users| prutter|account|admin)(?:$|\/)/.test(currentPath)) {35 if (/^\/(?:search|tag|type|archive|users|account|admin)(?:$|\/)/.test(currentPath)) { 36 36 _shouldIndex = false; 37 37 }
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)