Changeset d549549 in Klonkt for src/routes


Ignore:
Timestamp:
06/16/2026 09:41:28 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
029f047
Parents:
3f893bf
Message:

Statistics module (Phase 3): cookie-free tracking + premium stats page

Counters: posts.view_count, audio_tracks.play_count, stat_daily
(pageviews/day) + stat_visitor_day (unique visitors via daily rotating
salted hash of IP+UA, never stored → no cookie, no consent needed).
StatsService records pageview on home + post-view (skips admins), play on
the initial player stream fetch. /admin/stats (god-only +
premiumUnlocked-gated): cards + 14-day cookie-free bar chart
(visitors/views) + top posts/tracks. Dashboard button is premium-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

Location:
src/routes
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • src/routes/audio.js

    r3f893bf rd549549  
    2828import path from 'path';
    2929import { fileURLToPath } from 'url';
     30import db from '../config/database.js';
     31import { recordPlay } from '../services/StatsService.js';
    3032
    3133const __dirname = path.dirname(fileURLToPath(import.meta.url));
     
    9092  const range = req.headers.range;
    9193
     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
    92107  // Common headers
    93108  res.setHeader('Content-Type', mime);
  • src/routes/posts.js

    r3f893bf rd549549  
    88import { requireAuth } from '../middleware/auth.js';
    99import { renderPage } from '../middleware/render.js';
     10import { recordPageview, recordPostView } from '../services/StatsService.js';
    1011import PermissionsService from '../services/PermissionsService.js';
    1112import MarkdownService from '../services/MarkdownService.js';
     
    113114  `).all(site.id);
    114115
     116  recordPageview(site.id, req);
     117
    115118  renderPage(req, res, 'pages/home', {
    116119    pinnedPosts,
     
    391394    if (!canEdit) return res.status(403).send('Not published');
    392395  }
     396
     397  // Statistieken: tel de weergave (skipt beheerders + niet-gepubliceerd-eigen-preview).
     398  if (post.status === 'published') recordPostView(post, req);
    393399
    394400  // Render content. Content is now user-authored HTML (already sanitized on
Note: See TracChangeset for help on using the changeset viewer.