own tracking
This commit is contained in:
225
src/tracking/tracking-store.go
Normal file
225
src/tracking/tracking-store.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user