Statistik: anonymes Impression-Tracking mit ASN-Einordnung

Seitenaufrufe werden serverseitig pro (Tag, anonymisiertes Netz, Pfad)
gezaehlt -- nur auf den HTML-Seiten-Routen, nicht auf API/Assets. Die
Client-IP wird sofort maskiert (IPv4 /16, IPv6 /32); die vollstaendige
IP wird nie gespeichert oder weiterverwendet.

Optional loest eine lokale GeoLite2-ASN-DB (Env KVER_GEOIP_ASN) den
Netzbetreiber aus dem bereits anonymisierten Netz auf -- auch dafuer wird
nicht die volle IP herangezogen. Fehlt die DB, bleibt das Feld leer.

Neuer Endpunkt GET /stats/detail liefert die Aggregate; die Tabelle
impression speichert nur Zaehlwerte (kein Eventlog).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 23:21:17 +02:00
parent bbd4bc27ba
commit 662285c8b5
5 changed files with 248 additions and 2 deletions
+12 -2
View File
@@ -21,6 +21,11 @@ func main() {
}
defer db.Close()
// Optionale GeoLite2-ASN-Datenbank für die netzwerktopologische Einordnung
// der Impressions. Inaktiv, wenn KVER_GEOIP_ASN nicht gesetzt ist.
initGeoASN()
defer closeGeoASN()
srv := &http.Server{
Addr: envOr("KVER_ADDR", ":8080"),
Handler: routes(),
@@ -88,15 +93,20 @@ func routes() http.Handler {
r.Get("/auth/sessioninfo", handleSessionInfo)
r.Get("/auth/headerbar", handleHeaderbar)
r.Get("/stats", handleStats)
r.Get("/stats/detail", handleStatsDetail)
// HTML-Seiten: mit Impression-Tracking (anonymisiert, siehe stats.go). Nur
// echte Seitenaufrufe, nicht API/Assets.
r.With(trackImpression).Get("/", serveIndex)
// --- Entries ---
r.Get("/entry/feed/{page}", handleFeed)
r.Get("/entry/{pid}/votes", handleVotes)
r.Get("/entry/{pid}/thread", handleThread)
r.Get("/e/{pid}", serveEntryPage)
r.With(trackImpression).Get("/e/{pid}", serveEntryPage)
// --- User (öffentlich) ---
r.Get("/u/{username}", serveUserPage)
r.With(trackImpression).Get("/u/{username}", serveUserPage)
r.Get("/u/{username}/info", handleUserPage)
r.Get("/u/{username}/feed/{page}", handleUserFeed)