Eric Bower
·
2026-05-03
1package main
2
3import (
4 "flag"
5 "fmt"
6 "log/slog"
7 "os"
8 "time"
9
10 "github.com/picosh/pico/pkg/apps/auth"
11 "github.com/picosh/pico/pkg/db/postgres"
12)
13
14func main() {
15 monthPtr := flag.String("month", "", "target month in YYYY-MM format (default: previous month)")
16 backfill := flag.Bool("backfill", false, "aggregate all historical months up to the month before last")
17 dryRun := flag.Bool("dry-run", false, "print months that would be processed without running aggregation")
18 flag.Parse()
19
20 logger := slog.Default()
21 dbURL := os.Getenv("DATABASE_URL")
22 if dbURL == "" {
23 fmt.Fprintln(os.Stderr, "DATABASE_URL must be set")
24 os.Exit(1)
25 }
26 dbpool := postgres.NewDB(dbURL, logger)
27 defer func() { _ = dbpool.Close() }()
28
29 if *backfill {
30 runBackfill(dbpool, logger, *dryRun)
31 return
32 }
33
34 targetMonth := parseMonth(*monthPtr, logger)
35 if err := auth.RunAnalyticsAggregation(dbpool, logger, targetMonth); err != nil {
36 logger.Error("aggregation failed", "err", err)
37 os.Exit(1)
38 }
39}
40
41func runBackfill(dbpool *postgres.PsqlDB, logger *slog.Logger, dryRun bool) {
42 months, err := fetchHistoricalMonths(dbpool)
43 if err != nil {
44 logger.Error("failed to fetch historical months", "err", err)
45 os.Exit(1)
46 }
47 if dryRun {
48 fmt.Println("Months to backfill:")
49 for _, m := range months {
50 fmt.Println(" ", m.Format("2006-01"))
51 }
52 return
53 }
54 for _, m := range months {
55 if err := auth.RunAnalyticsAggregation(dbpool, logger, m); err != nil {
56 logger.Error("aggregation failed for month", "month", m.Format("2006-01"), "err", err)
57 }
58 }
59 logger.Info("backfill complete", "months", len(months))
60}
61
62// fetchHistoricalMonths returns all distinct months that have data in analytics_visits,
63// excluding the current month and the previous month (handled by the auth service cron).
64func fetchHistoricalMonths(dbpool *postgres.PsqlDB) ([]time.Time, error) {
65 cutoff := time.Now().AddDate(0, -1, 0)
66 cutoffMonth := time.Date(cutoff.Year(), cutoff.Month(), 1, 0, 0, 0, 0, time.UTC)
67
68 rows, err := dbpool.Db.Queryx(`
69 SELECT DISTINCT date_trunc('month', created_at)::date AS month_start
70 FROM analytics_visits
71 WHERE created_at < $1
72 ORDER BY month_start ASC
73 `, cutoffMonth)
74 if err != nil {
75 return nil, err
76 }
77 defer func() { _ = rows.Close() }()
78
79 var months []time.Time
80 for rows.Next() {
81 var monthDate time.Time
82 if err := rows.Scan(&monthDate); err != nil {
83 return nil, err
84 }
85 months = append(months, monthDate)
86 }
87 return months, rows.Err()
88}
89
90func parseMonth(arg string, logger *slog.Logger) time.Time {
91 now := time.Now()
92 if arg != "" {
93 t, err := time.Parse("2006-01", arg)
94 if err != nil {
95 logger.Error("invalid month format, use YYYY-MM", "err", err, "input", arg)
96 os.Exit(1)
97 }
98 return t
99 }
100 return now.AddDate(0, -1, 0)
101}