Changeset d549549 in Klonkt
- Timestamp:
- 06/16/2026 09:41:28 AM (3 months ago)
- Branches:
- main
- Children:
- 029f047
- Parents:
- 3f893bf
- Location:
- src
- Files:
-
- 3 added
- 5 edited
-
config/database.js (modified) (2 diffs)
-
routes/admin-stats.js (added)
-
routes/audio.js (modified) (2 diffs)
-
routes/posts.js (modified) (3 diffs)
-
server.js (modified) (2 diffs)
-
services/StatsService.js (added)
-
views/pages/admin-stats.ejs (added)
-
views/pages/admin.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r3f893bf rd549549 71 71 ensureColumn('posts', 'type', "TEXT DEFAULT 'post'"); // post | foto | video | audio 72 72 73 // Statistieken (premium-module) — kale tellers, cookievrij. 74 ensureColumn('posts', 'view_count', 'INTEGER DEFAULT 0'); // weergaven per post 75 ensureColumn('audio_tracks', 'play_count', 'INTEGER DEFAULT 0'); // plays per track 76 73 77 // Playlists (v9 feature) — first-class entity. CREATE IF NOT EXISTS is 74 78 // idempotent so it's safe to run on every boot regardless of DB age. … … 108 112 `); 109 113 db.prepare("INSERT OR IGNORE INTO app_settings (key, value) VALUES ('tenancy', 'solo')").run(); 114 115 // ── Statistieken (premium) — cookievrij ───────────────────── 116 // stat_daily: per dag per site het aantal pageviews (kale teller). 117 // stat_visitor_day: per dag per site een rij per UNIEKE bezoeker-hash 118 // (sha256 van IP+UA+dag-salt; de salt roteert dagelijks en wordt nooit 119 // bewaard → geen persistente identifier, geen cookie, geen toestemming nodig). 120 db.exec(` 121 CREATE TABLE IF NOT EXISTS stat_daily ( 122 site_id TEXT NOT NULL, 123 day TEXT NOT NULL, 124 pageviews INTEGER NOT NULL DEFAULT 0, 125 PRIMARY KEY (site_id, day) 126 ); 127 CREATE TABLE IF NOT EXISTS stat_visitor_day ( 128 site_id TEXT NOT NULL, 129 day TEXT NOT NULL, 130 visitor_hash TEXT NOT NULL, 131 PRIMARY KEY (site_id, day, visitor_hash) 132 ); 133 CREATE INDEX IF NOT EXISTS idx_stat_visitor_day ON stat_visitor_day(site_id, day); 134 `); 110 135 111 136 // ── Cirkels (federatie) ───────────────────────────────────── -
src/routes/audio.js
r3f893bf rd549549 28 28 import path from 'path'; 29 29 import { fileURLToPath } from 'url'; 30 import db from '../config/database.js'; 31 import { recordPlay } from '../services/StatsService.js'; 30 32 31 33 const __dirname = path.dirname(fileURLToPath(import.meta.url)); … … 90 92 const range = req.headers.range; 91 93 94 // Statistieken: tel één play bij de initiële player-fetch (niet bij scrub/ 95 // range-continuaties; replays binnen 24u komen uit de browsercache → geen 96 // dubbeltelling). Best-effort, mag nooit de stream breken. 97 if (req.get('X-Audio-Player') === '1' && (!range || /^bytes=0-/.test(range))) { 98 try { 99 const tr = db.prepare(` 100 SELECT t.id FROM audio_tracks t JOIN media m ON t.media_id = m.id 101 WHERE m.storage_path = ? OR m.storage_path LIKE ? LIMIT 1 102 `).get(filename, '%' + filename); 103 if (tr) recordPlay(tr.id); 104 } catch {} 105 } 106 92 107 // Common headers 93 108 res.setHeader('Content-Type', mime); -
src/routes/posts.js
r3f893bf rd549549 8 8 import { requireAuth } from '../middleware/auth.js'; 9 9 import { renderPage } from '../middleware/render.js'; 10 import { recordPageview, recordPostView } from '../services/StatsService.js'; 10 11 import PermissionsService from '../services/PermissionsService.js'; 11 12 import MarkdownService from '../services/MarkdownService.js'; … … 113 114 `).all(site.id); 114 115 116 recordPageview(site.id, req); 117 115 118 renderPage(req, res, 'pages/home', { 116 119 pinnedPosts, … … 391 394 if (!canEdit) return res.status(403).send('Not published'); 392 395 } 396 397 // Statistieken: tel de weergave (skipt beheerders + niet-gepubliceerd-eigen-preview). 398 if (post.status === 'published') recordPostView(post, req); 393 399 394 400 // Render content. Content is now user-authored HTML (already sanitized on -
src/server.js
r3f893bf rd549549 49 49 import adminUpdatesRoutes from './routes/admin-updates.js'; 50 50 import adminPatreonRoutes from './routes/admin-patreon.js'; 51 import adminStatsRoutes from './routes/admin-stats.js'; 51 52 import circleRoutes from './routes/circle.js'; 52 53 … … 241 242 app.use('/admin/updates', adminUpdatesRoutes); 242 243 app.use('/admin/patreon', adminPatreonRoutes); 244 app.use('/admin/stats', adminStatsRoutes); 243 245 app.use('/admin', adminRoutes); 244 246 app.use('/prutter', prutterRoutes); -
src/views/pages/admin.ejs
r3f893bf rd549549 29 29 <a href="/admin/settings" class="btn">⚙️ Instellingen</a> 30 30 <% } %> 31 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %> 32 <a href="/admin/stats" class="btn">📊 Statistieken</a> 33 <% } %> 31 34 <a href="/admin/updates" class="btn">🔄 Updates</a> 32 35 </nav>
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)