package db import ( "context" "errors" "fmt" "net" "net/http" "strings" "time" ) const ( AnalyticsGranularityDaily = "1d" AnalyticsGranularityWeekly = "1w" ) const ( Day = time.Hour * 24 AnalyticsRetention90d = Day * 90 AnalyticsRetention366d = Day * 366 AnalyticsRetention732d = Day * 732 AnalyticsRetention90dStr = "90d" AnalyticsRetention366dStr = "366d" AnalyticsRetention732dStr = "732d" ) var ErrAnalyticsIp = errors.New("error parsing ip for analytics") func (d *DB) InsertAnalytics(backhalfId int, r *http.Request) error { cfg := d.GetConfig() if d.adb == nil || cfg.AnalyticsDisable { return nil } sql := `SELECT b.analytics_collect, b.analytics_granularity FROM back_half b WHERE b.id = $1;` row := d.db.QueryRow(sql, backhalfId) var shouldCollect bool var granularity string err := row.Scan(&shouldCollect, &granularity) if err != nil { return err } if !shouldCollect { return nil } ipStr, _, _ := strings.Cut(r.RemoteAddr, ":") ip := net.ParseIP(ipStr) if ip == nil { return fmt.Errorf("%w: %s", ErrAnalyticsIp, ipStr) } ua := r.Header.Get("User-Agent") upsertUaSql := `INSERT INTO ua (name) VALUES ($1) ON CONFLICT DO UPDATE SET name = excluded.name RETURNING id;` aWriteTimeout := time.Duration(cfg.AnalyticsWriteTimeout) * time.Second ctx, cancel := context.WithTimeout(context.Background(), aWriteTimeout) var uaId int row = d.adb.QueryRowContext(ctx, upsertUaSql, ua) err = row.Scan(&uaId) cancel() if err != nil { return err } ctx, cancel = context.WithTimeout(context.Background(), aWriteTimeout) defer cancel() var upsertAnalyticsSql = `INSERT INTO analytics (back_half_id, start, netmask, ua_id, count) VALUES ($1, $2, $3, $4, $5) ON CONFLICT DO UPDATE SET count = excluded.count + new.count;` _, err = d.adb.ExecContext(ctx, upsertAnalyticsSql, backhalfId, ua) if err != nil { return err } return nil }