Changeset e3a5aed in Klonkt for src/services/StatsService.js


Ignore:
Timestamp:
06/30/2026 10:11:03 PM (2 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
d9c5eb8
Parents:
65a0697
Message:

feat(stats): let the admin exclude their own IP from statistics

Logged-in admins were already skipped, but an admin browsing logged out (incognito, another browser)
still inflated the numbers. Admin -> Statistics now shows your current IP with a one-click "Don't
count my visits" toggle, stored in a stats_exclude_ips app_setting and checked on every pageview
(recordPageview/recordPostView). Trust-proxy gives the real client IP; normalised for matching.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/StatsService.js

    r65a0697 re3a5aed  
    5454}
    5555
     56// The client IP (trust-proxy gives the real one), normalised: drop an IPv4-mapped-IPv6 prefix
     57// and a trailing :port so it matches what the admin sees + stores.
     58function clientIp(req) {
     59  let ip = (req && (req.ip || (req.socket && req.socket.remoteAddress))) || '';
     60  if (ip.startsWith('::ffff:')) ip = ip.slice(7);
     61  if (/^\d{1,3}(\.\d{1,3}){3}:\d+$/.test(ip)) ip = ip.split(':')[0];
     62  return ip;
     63}
     64export function currentIp(req) { return clientIp(req); }
     65
     66// Admin-configured IPs to skip — so an owner browsing logged-OUT (incognito, another browser)
     67// doesn't inflate their own stats. Stored as a comma-separated app_setting.
     68export function getExcludedIps() {
     69  return (getSetting('stats_exclude_ips', '') || '').split(',').map((s) => s.trim()).filter(Boolean);
     70}
     71export function setExcludedIps(list) {
     72  const clean = [...new Set((list || []).map((s) => String(s).trim()).filter(Boolean))].slice(0, 20);
     73  setSetting('stats_exclude_ips', clean.join(','));
     74}
     75function isExcludedIp(req) {
     76  try { const ip = clientIp(req); return !!ip && getExcludedIps().includes(ip); } catch { return false; }
     77}
     78
    5679// Lazy prepares — tables only exist after initializeDatabase(); this module is
    5780// imported before that call.
     
    90113
    91114export function recordPageview(siteId, req) {
    92   if (!siteId || isOperator(req) || isBot(req)) return;
     115  if (!siteId || isOperator(req) || isBot(req) || isExcludedIp(req)) return;
    93116  try {
    94117    const d = today();
     
    100123
    101124export function recordPostView(post, req) {
    102   if (!post || !post.id || isOperator(req) || isBot(req)) return;
     125  if (!post || !post.id || isOperator(req) || isBot(req) || isExcludedIp(req)) return;
    103126  try {
    104127    stmts().bumpPost.run(post.id);
Note: See TracChangeset for help on using the changeset viewer.