own tracking

This commit is contained in:
JurajKubrican
2025-08-15 23:13:47 +02:00
parent ebfd1fe9f1
commit 3249df5802
10 changed files with 531 additions and 3 deletions

View File

@@ -0,0 +1,225 @@
package tracking
import (
"database/sql"
"sync"
"github.com/labstack/echo/v4"
_ "modernc.org/sqlite"
)
type TrackingService struct {
db *sql.DB
mu sync.RWMutex // Add mutex for thread safety
}
var trackingService *TrackingService
func StartTrackingService() *TrackingService {
if trackingService == nil {
db, err := sql.Open("sqlite", "./data/tracking.db")
if err != nil {
panic(err)
}
// Apply SQLite settings explicitly with PRAGMA statements
pragmas := []string{
"PRAGMA busy_timeout = 10000", // 10 second busy timeout
"PRAGMA journal_mode = WAL", // Write-Ahead Logging mode
"PRAGMA synchronous = NORMAL", // Normal synchronization
"PRAGMA foreign_keys = ON", // Enable foreign key constraints
"PRAGMA temp_store = MEMORY", // Store temp tables in memory
}
for _, pragma := range pragmas {
if _, err := db.Exec(pragma); err != nil {
println("Warning: Failed to set pragma:", pragma, "Error:", err.Error())
}
}
// With mutex protection, we can use a single connection
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
trackingService = &TrackingService{db: db}
err = trackingService.initDB()
if err != nil {
panic(err)
}
}
return trackingService
}
func (ts *TrackingService) AddVisit(c echo.Context) {
ts.mu.Lock()
defer ts.mu.Unlock()
query := `INSERT INTO user_visits (ip, path, method, fingerprint, user_agent, referer, accept_language)
VALUES (?, ?, ?, ?, ?, ?, ?)`
fingerprint := getFingerprint(c)
referer := c.Request().Header.Get("Referer")
userAgent := c.Request().Header.Get("User-Agent")
acceptLanguage := c.Request().Header.Get("Accept-Language")
ip := c.RealIP()
path := c.Request().URL.Path
method := c.Request().Method
_, err := ts.db.Exec(query,
ip,
path,
method,
fingerprint,
userAgent,
referer,
acceptLanguage)
if err != nil {
// Log error instead of panic to prevent crashes
println("Error adding visit:", err.Error())
}
}
func (ts *TrackingService) initDB() error {
query := `
CREATE TABLE IF NOT EXISTS user_visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
path TEXT NOT NULL,
method TEXT NOT NULL,
fingerprint TEXT,
user_agent TEXT,
referer TEXT,
accept_language TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
`
_, err := ts.db.Exec(query)
return err
}
func (ts *TrackingService) GetTotalVisits() int {
ts.mu.RLock()
defer ts.mu.RUnlock()
var count int
query := `SELECT COUNT(*) FROM user_visits`
err := ts.db.QueryRow(query).Scan(&count)
if err != nil {
println("Error getting total visits:", err.Error())
return 0
}
return count
}
func (ts *TrackingService) GetUniqueVisitors() int {
ts.mu.RLock()
defer ts.mu.RUnlock()
var count int
query := `SELECT COUNT(DISTINCT fingerprint) FROM user_visits`
err := ts.db.QueryRow(query).Scan(&count)
if err != nil {
println("Error getting unique visitors:", err.Error())
return 0
}
return count
}
func (ts *TrackingService) GetTodayVisits() int {
ts.mu.RLock()
defer ts.mu.RUnlock()
var count int
query := `SELECT COUNT(*) FROM user_visits WHERE DATE(timestamp) = DATE('now')`
err := ts.db.QueryRow(query).Scan(&count)
if err != nil {
println("Error getting today visits:", err.Error())
return 0
}
return count
}
func (ts *TrackingService) GetActiveSessions() int {
ts.mu.RLock()
defer ts.mu.RUnlock()
var count int
query := `SELECT COUNT(DISTINCT fingerprint) FROM user_visits WHERE timestamp >= datetime('now', '-30 minutes')`
err := ts.db.QueryRow(query).Scan(&count)
if err != nil {
println("Error getting active sessions:", err.Error())
return 0
}
return count
}
func (ts *TrackingService) GetRecentVisits(limit int) []VisitDisplay {
ts.mu.RLock()
defer ts.mu.RUnlock()
query := `SELECT timestamp, ip, path, user_agent, referer FROM user_visits ORDER BY timestamp DESC LIMIT ?`
rows, err := ts.db.Query(query, limit)
if err != nil {
println("Error getting recent visits:", err.Error())
return []VisitDisplay{}
}
defer rows.Close()
var visits []VisitDisplay
for rows.Next() {
var visit VisitDisplay
if err := rows.Scan(&visit.Timestamp, &visit.IPAddress, &visit.Path, &visit.UserAgent, &visit.Referrer); err != nil {
println("Error scanning visit:", err.Error())
continue
}
visits = append(visits, visit)
}
return visits
}
func (ts *TrackingService) GetTopPages(limit int) []PageStat {
ts.mu.RLock()
defer ts.mu.RUnlock()
// First get total visits count
var totalVisits int
totalQuery := `SELECT COUNT(*) FROM user_visits`
err := ts.db.QueryRow(totalQuery).Scan(&totalVisits)
if err != nil {
println("Error getting total visits for top pages:", err.Error())
return []PageStat{}
}
// Then get the top pages
query := `SELECT path, COUNT(*) as count FROM user_visits GROUP BY path ORDER BY count DESC LIMIT ?`
rows, err := ts.db.Query(query, limit)
if err != nil {
println("Error getting top pages:", err.Error())
return []PageStat{}
}
defer rows.Close()
var pages []PageStat
for rows.Next() {
var page PageStat
if err := rows.Scan(&page.Path, &page.Count); err != nil {
println("Error scanning page:", err.Error())
continue
}
// Calculate percentage using the totalVisits we got earlier
if totalVisits > 0 {
page.Percentage = float64(page.Count) / float64(totalVisits) * 100
}
pages = append(pages, page)
}
return pages
}