analytics_int

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: GPL-3.0 Imports: 36 Imported by: 0

README

Internal Analytics Module

Server-side analytics for oCMS. Tracks page views, unique visitors, referrers, browser/device stats, and geographic data with privacy-focused anonymization.

Features

  • Page view tracking with session detection and bounce rate
  • Read tracking — Medium.com-style engagement metrics (scroll depth + time on page)
  • Post statistics display — view and read counts shown on posts (toggle in settings)
  • Views & Reads report — admin report page with per-page view/read counts and read rate
  • Privacy-first: IP anonymization, rotating visitor hashes, no raw IP storage
  • GeoIP country detection (optional, via MaxMind GeoLite2)
  • Background aggregation into hourly/daily statistics
  • IP exclusion for filtering internal/test traffic
  • Self-referral filtering using the configured site URL
  • Configurable retention with automatic cleanup

Settings

Configured via Admin > Internal Analytics > Settings.

Setting Description
Enabled Toggle analytics tracking on/off
Show post statistics Display view/read counts on public post pages (default: on)
Retention days How long to keep analytics data (default: 365)
Excluded paths URL prefixes to skip (one per line)
Excluded IPs IPs or CIDRs to exclude from tracking (one per line)
IP Exclusion

Add IPs or CIDR ranges to exclude from tracking. Useful for filtering pen testing traffic, office IPs, or monitoring services.

# Exact IPs
203.0.113.50
198.51.100.178

# CIDR ranges
10.0.0.0/8
192.168.0.0/16

Traffic from excluded IPs is silently dropped before any data is recorded.

Self-Referral Filtering

The module automatically strips self-referrals using the site URL configured in Admin > Site Configuration (site_url key). When a page view's referrer domain matches the site domain (case-insensitive, with/without www. prefix), the referrer is cleared so it doesn't appear in referrer reports.

For example, if site_url is https://www.it-digest.info, referrers from it-digest.info, www.it-digest.info, IT-DIGEST.INFO, etc. are all filtered.

Purge Self-Referral Data

A one-time purge action is available in the settings to clean up existing self-referral data from the database. This removes matching entries from both raw views and aggregated referrer statistics. Requires site_url to be configured.

Routes

Method Path Description
GET /admin/internal-analytics Dashboard
GET /admin/internal-analytics/report Views & Reads report
GET /admin/internal-analytics/api/stats JSON stats (HTMX)
GET /admin/internal-analytics/api/realtime Real-time visitor count
POST /admin/internal-analytics/settings Save settings
POST /admin/internal-analytics/aggregate Trigger aggregation
POST /analytics/read Read tracking beacon (public)

Database Tables

  • page_analytics_views - Raw page view events
  • page_analytics_reads - Read events (scroll depth + time on page)
  • page_analytics_hourly - Hourly aggregates
  • page_analytics_daily - Daily aggregates
  • page_analytics_referrers - Daily referrer stats
  • page_analytics_tech - Browser/OS/device stats
  • page_analytics_geo - Geographic stats
  • page_analytics_settings - Module configuration

Environment Variables

Variable Description
OCMS_GEOIP_DB_PATH Path to GeoLite2-Country.mmdb for country detection

Read Tracking

The module tracks "reads" as a measure of content engagement, similar to Medium.com. A page view becomes a "read" when the visitor:

  1. Scrolls through at least 60% of the page content
  2. Spends at least 30 seconds on the page
How It Works

A small inline JavaScript snippet is injected into post pages via the analyticsIntReadTracker template function. The script:

  • Monitors scroll position relative to .page-content or .st-article__body
  • Tracks time spent on the page
  • Sends a single POST /analytics/read beacon when both thresholds are met
  • Uses navigator.sendBeacon() for reliable delivery (XHR fallback)
  • Only fires once per page load

The beacon sends: { "path": "/slug", "scroll_depth": 75, "time_on_page": 45 }

Deduplication

Reads are deduplicated by a UNIQUE index on (session_hash, path) — the same visitor session can only record one read per page, regardless of how many times they revisit.

Privacy

Read tracking uses the same privacy-focused approach as page view tracking:

  • No personal data stored — visitor and session are anonymized hashes
  • Hashes rotate with the daily salt
  • No cookies or local storage used
Template Functions

The module provides three template functions for themes:

Function Description
analyticsPostStats(slug) Returns PageStats{Views, Reads} for a post
analyticsShowPostStats() Returns true if post stats display is enabled
analyticsIntReadTracker(nonce) Returns inline <script> for read tracking
Theme Integration

Both the default and starter themes display view/read counts on post pages (below the title). To customize, override the theme template and use the template functions above.

Example:

{{if and (eq .Page.Type "post") (analyticsShowPostStats)}}
{{$stats := analyticsPostStats .Page.Slug}}
<div class="post-stats">
    <span>{{$stats.Views}} {{TTheme $.LangCode "frontend.views"}}</span>
    <span>{{$stats.Reads}} {{TTheme $.LangCode "frontend.reads"}}</span>
</div>
{{end}}

Documentation

Overview

Package analytics_int provides built-in server-side analytics for oCMS. It tracks page views, unique visitors, referrers, browser/device stats, and geographic data with privacy-focused anonymization.

templ: version: v0.3.1001

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AnalyticsIntPage added in v0.9.0

func AnalyticsIntPage(pc *adminviews.PageContext, data AnalyticsIntViewData) templ.Component

func AnalyticsIntReportPage added in v0.18.0

func AnalyticsIntReportPage(pc *adminviews.PageContext, data ReportViewData) templ.Component

Types

type AnalyticsIntViewData added in v0.9.0

type AnalyticsIntViewData struct {
	Overview     OverviewStats
	TopPages     []TopPage
	TopReferrers []TopReferrer
	Browsers     []BrowserStat
	Devices      []DeviceStat
	Countries    []CountryStat
	TimeSeries   []TimeSeriesPoint
	DateRange    string
	Settings     Settings
}

type BrowserStat

type BrowserStat struct {
	Browser string
	Views   int64
	Percent float64
}

BrowserStat represents browser breakdown for dashboard.

type CountryStat

type CountryStat struct {
	CountryCode string
	CountryName string
	Views       int64
	Percent     float64
}

CountryStat represents country breakdown for dashboard.

type DailyStat

type DailyStat struct {
	ID             int64
	Date           time.Time
	Path           string
	Views          int
	UniqueVisitors int
	Bounces        int
}

DailyStat represents daily aggregated statistics.

type DashboardData

type DashboardData struct {
	Overview     OverviewStats
	TopPages     []TopPage
	TopReferrers []TopReferrer
	Browsers     []BrowserStat
	Devices      []DeviceStat
	Countries    []CountryStat
	TimeSeries   []TimeSeriesPoint
	DateRange    string // "7d", "30d", "90d", "1y"
	Settings     Settings
}

DashboardData contains all data for the analytics dashboard.

type DeviceStat

type DeviceStat struct {
	DeviceType string
	Views      int64
	Percent    float64
}

DeviceStat represents device type breakdown for dashboard.

type GeoStat

type GeoStat struct {
	ID             int64
	Date           time.Time
	CountryCode    string
	Views          int
	UniqueVisitors int
}

GeoStat represents daily geographic statistics.

type HourlyStat

type HourlyStat struct {
	ID             int64
	HourStart      time.Time
	Path           string
	Views          int
	UniqueVisitors int
}

HourlyStat represents hourly aggregated statistics.

type Module

type Module struct {
	module.BaseModule
	// contains filtered or unexported fields
}

Module implements the internal analytics module.

func New

func New() *Module

New creates a new internal analytics module.

func (*Module) AdminURL

func (m *Module) AdminURL() string

AdminURL returns the admin dashboard URL.

func (*Module) CreateSessionHash

func (m *Module) CreateSessionHash(ip, userAgent string) string

CreateSessionHash creates a session proxy hash for grouping page views. Similar to visitor hash but with different suffix for distinct identification.

func (*Module) CreateVisitorHash

func (m *Module) CreateVisitorHash(ip, userAgent string) string

CreateVisitorHash creates an anonymized visitor fingerprint hash. The hash combines anonymized IP + user agent + date + salt. This allows counting unique visitors per day without tracking across days.

func (*Module) GetRealTimeVisitorCount

func (m *Module) GetRealTimeVisitorCount(minutes int) int

GetRealTimeVisitorCount returns the number of unique visitors in the last N minutes.

func (*Module) GetTrackingMiddleware

func (m *Module) GetTrackingMiddleware() func(next http.Handler) http.Handler

GetTrackingMiddleware returns the tracking middleware for use in router setup. This should be called after Init() to ensure settings are loaded.

func (*Module) Init

func (m *Module) Init(ctx *module.Context) error

Init initializes the module.

func (*Module) IsEnabled

func (m *Module) IsEnabled() bool

IsEnabled returns whether analytics tracking is enabled.

func (*Module) Migrations

func (m *Module) Migrations() []module.Migration

Migrations returns database migrations.

func (*Module) RegisterAdminRoutes

func (m *Module) RegisterAdminRoutes(r chi.Router)

RegisterAdminRoutes registers admin routes.

func (*Module) RegisterRoutes

func (m *Module) RegisterRoutes(r chi.Router)

RegisterRoutes registers public routes. POST /analytics/read is an analytics beacon endpoint — intentionally CSRF-exempt because it only records anonymous engagement data (no session state changed, no user data modified). Frontend JS sends beacons via navigator.sendBeacon. Rate limited to 2 req/s per IP with burst of 5 to prevent abuse.

func (*Module) ReloadSettings

func (m *Module) ReloadSettings() error

ReloadSettings reloads settings from the database.

func (*Module) RunAggregationNow

func (m *Module) RunAggregationNow() error

RunAggregationNow runs all aggregation jobs immediately (for testing).

func (*Module) RunFullAggregation added in v0.3.0

func (m *Module) RunFullAggregation(ctx context.Context) (int, error)

RunFullAggregation aggregates all historical raw data into daily stats. This backfills page_analytics_daily from page_analytics_views for all past dates.

func (*Module) Shutdown

func (m *Module) Shutdown() error

Shutdown cleans up resources.

func (*Module) SidebarLabel

func (m *Module) SidebarLabel() string

SidebarLabel returns the display label for the admin sidebar.

func (*Module) StartAggregator

func (m *Module) StartAggregator()

StartAggregator starts background aggregation jobs.

Uses interval-based scheduling (@every) instead of fixed cron times (e.g., "5 * * * *") to ensure jobs run on auto-stop platforms like Fly.io where the machine may not be running at specific clock times. With fixed schedules, a machine that starts at :08 and stops at :55 would always miss the :05 hourly job.

func (*Module) TemplateFuncs

func (m *Module) TemplateFuncs() template.FuncMap

TemplateFuncs returns template functions provided by the module.

func (*Module) TrackingMiddleware

func (m *Module) TrackingMiddleware() func(http.Handler) http.Handler

TrackingMiddleware returns middleware that tracks page views.

func (*Module) TranslationsFS

func (m *Module) TranslationsFS() embed.FS

TranslationsFS returns module translations.

type OverviewStats

type OverviewStats struct {
	TotalViews       int64
	UniqueVisitors   int64
	BounceRate       float64 // percentage
	ViewsToday       int64
	ViewsYesterday   int64
	TrendPercent     float64 // change from yesterday
	RealTimeVisitors int     // visitors in last 5 minutes
}

OverviewStats contains summary statistics for the dashboard.

type PageRead added in v0.18.0

type PageRead struct {
	ID          int64
	VisitorHash string
	Path        string
	PageID      *int64
	SessionHash string
	ScrollDepth int // Percentage of content scrolled (0-100)
	TimeOnPage  int // Seconds spent on page
	CreatedAt   time.Time
}

PageRead represents a single read event stored in the database. A "read" occurs when a visitor scrolls through ≥60% of the content and spends ≥30 seconds on the page (Medium.com-style engagement).

type PageStats added in v0.18.0

type PageStats struct {
	Views int64
	Reads int64
}

PageStats holds combined view and read counts for a page (used in templates).

type PageStatsRow added in v0.18.0

type PageStatsRow struct {
	Path      string
	PageTitle string
	PageType  string
	Views     int64
	Reads     int64
	ReadRate  float64 // reads/views percentage
}

PageStatsRow represents a row in the views/reads admin report.

type PageView

type PageView struct {
	ID             int64
	VisitorHash    string    // Anonymized visitor fingerprint (daily rotating)
	Path           string    // URL path (e.g., "/about")
	PageID         *int64    // FK to pages.id (nullable for non-page routes)
	ReferrerDomain string    // Extracted referrer domain
	CountryCode    string    // 2-letter ISO country code
	Browser        string    // Browser name (Chrome, Firefox, Safari, etc.)
	OS             string    // Operating system
	DeviceType     string    // desktop, mobile, tablet
	Language       string    // Accept-Language primary language
	SessionHash    string    // Session proxy (IP+UA+date hash)
	CreatedAt      time.Time // When the view occurred
}

PageView represents a single page view event stored in the database.

type ParsedUA

type ParsedUA struct {
	Browser    string
	OS         string
	DeviceType string // "desktop", "mobile", "tablet"
}

ParsedUA holds parsed user agent information.

type ReadRequest added in v0.18.0

type ReadRequest struct {
	Path        string `json:"path"`
	ScrollDepth int    `json:"scroll_depth"`
	TimeOnPage  int    `json:"time_on_page"`
}

ReadRequest represents a read tracking beacon request from the frontend.

type ReferrerStat

type ReferrerStat struct {
	ID             int64
	Date           time.Time
	ReferrerDomain string
	Views          int
	UniqueVisitors int
}

ReferrerStat represents daily referrer statistics.

type ReportViewData added in v0.18.0

type ReportViewData struct {
	Rows       []PageStatsRow
	DateRange  string
	Pagination adminviews.PaginationData
}

ReportViewData holds data for the views/reads admin report page.

type Settings

type Settings struct {
	Enabled           bool
	RetentionDays     int
	ExcludePaths      []string
	CurrentSalt       string
	SaltCreatedAt     time.Time
	SaltRotationHours int
	ShowPostStats     bool
}

Settings holds module configuration.

type TechStat

type TechStat struct {
	ID         int64
	Date       time.Time
	Browser    string
	OS         string
	DeviceType string
	Views      int
}

TechStat represents daily browser/device statistics.

type TimeSeriesPoint

type TimeSeriesPoint struct {
	Date   string // YYYY-MM-DD
	Views  int64
	Unique int64
}

TimeSeriesPoint represents a data point for time series charts.

type TopPage

type TopPage struct {
	Path           string
	PageID         *int64
	PageTitle      string
	Views          int64
	UniqueVisitors int64
	Reads          int64
	BounceRate     float64
}

TopPage represents a page in the top pages list.

type TopReferrer

type TopReferrer struct {
	Domain         string
	Views          int64
	UniqueVisitors int64
}

TopReferrer represents a referrer in the top referrers list.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL