Changeset 1794fac in Klonkt for src/services/StatsService.js


Ignore:
Timestamp:
06/19/2026 04:07:24 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
6be57b4
Parents:
37edecd
Message:

Pro stats (premium feature #5): referrers + period selector + all-time

Extension of the cookie-free StatsService.

  • DB: stat_referrer (site_id, host, count).
  • StatsService: recordPageview now also counts the external referrer host (Referer header; empty/own-site/invalid skipped, www- normalised). getStats(days) clamps to 7/14/30/90 + returns top-10 referrers (instance-wide) + all-time (pageviews, visitor days).
  • admin-stats route: ?days=7|14|30|90.
  • admin-stats view: period buttons, dynamic labels, Sources section, all-time row.

node --check passed.

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/StatsService.js

    r37edecd r1794fac  
    5858    bumpPost: db.prepare('UPDATE posts SET view_count = COALESCE(view_count, 0) + 1 WHERE id = ?'),
    5959    bumpTrack: db.prepare('UPDATE audio_tracks SET play_count = COALESCE(play_count, 0) + 1 WHERE id = ?'),
     60    bumpReferrer: db.prepare(`
     61      INSERT INTO stat_referrer (site_id, host, count) VALUES (?, ?, 1)
     62      ON CONFLICT(site_id, host) DO UPDATE SET count = count + 1
     63    `),
    6064  };
    6165  return _s;
     66}
     67
     68// Externe referrer-host uit de Referer-header (pro-stats #5). Lege/eigen-site/
     69// ongeldige referrers worden overgeslagen → alleen echte externe bronnen tellen.
     70function recordReferrer(siteId, req) {
     71  try {
     72    const ref = req && req.headers && (req.headers.referer || req.headers.referrer);
     73    if (!ref) return;
     74    const host = new URL(ref).host.replace(/^www\./, '').toLowerCase();
     75    if (!host) return;
     76    const own = ((req.headers && req.headers.host) || '').replace(/^www\./, '').toLowerCase();
     77    if (host === own) return; // interne navigatie telt niet als bron
     78    stmts().bumpReferrer.run(siteId, host.slice(0, 120));
     79  } catch { /* geen geldige referrer-URL → overslaan */ }
    6280}
    6381
     
    6886    stmts().bumpDaily.run(siteId, d);
    6987    stmts().addVisitor.run(siteId, d, visitorHash(req));
     88    recordReferrer(siteId, req);
    7089  } catch { /* statistieken mogen nooit een request breken */ }
    7190}
     
    86105// Instance-brede statistieken (solo = de site, hub = alle sites samen).
    87106export function getStats(days = 14) {
     107  days = [7, 14, 30, 90].includes(Number(days)) ? Number(days) : 14;
    88108  const pvMap = Object.fromEntries(
    89109    db.prepare('SELECT day, SUM(pageviews) AS pv FROM stat_daily GROUP BY day').all().map((r) => [r.day, r.pv]),
     
    113133    ORDER BY play_count DESC LIMIT 5
    114134  `).all();
    115   return { totals, series, topPosts, topTracks, days };
     135  // Top externe bronnen (pro #5) — instance-breed geaggregeerd per host.
     136  let referrers = [];
     137  try {
     138    referrers = db.prepare(
     139      'SELECT host, SUM(count) AS n FROM stat_referrer GROUP BY host ORDER BY n DESC LIMIT 10'
     140    ).all();
     141  } catch { referrers = []; }
     142  // All-time totalen (cookieloze unieke bezoekers = som van dag-uniques).
     143  const allTime = {
     144    pageviews: db.prepare('SELECT COALESCE(SUM(pageviews),0) AS n FROM stat_daily').get().n,
     145    visitorDays: db.prepare('SELECT COUNT(*) AS n FROM stat_visitor_day').get().n,
     146  };
     147  return { totals, series, topPosts, topTracks, referrers, allTime, days };
    116148}
Note: See TracChangeset for help on using the changeset viewer.