refactor: remove automatic background incremental sync

Garmin's rate-limiting means every unattended sync attempt risks a
ban; syncing should only ever happen when explicitly triggered via
"Sync now" (POST /api/sync/run), never on an unattended timer.

Removes the periodic background loop (main.go's runIncrementalSyncLoop,
api.Server.RunIncrementalSyncForAllUsers), its GENIUSRUN_INCREMENTAL_SYNC_EVERY
config, and store.DB.ListUsers (which existed solely to feed it). The
manual "Sync now" flow (Backfill/IncrementalSync/FillPendingDetails via
FullSync) is untouched.
This commit is contained in:
2026-07-26 18:01:41 +02:00
parent 7346e2eadd
commit 4d2cbe4883
4 changed files with 1 additions and 66 deletions

View File

@@ -62,8 +62,6 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop() defer stop()
go runIncrementalSyncLoop(ctx, server, cfg.IncrementalSyncEvery)
httpServer := &http.Server{Addr: cfg.Addr, Handler: server.Router()} httpServer := &http.Server{Addr: cfg.Addr, Handler: server.Router()}
go func() { go func() {
log.Printf("geniusrund listening on %s", cfg.Addr) log.Printf("geniusrund listening on %s", cfg.Addr)
@@ -80,19 +78,3 @@ func main() {
log.Printf("http server shutdown: %v", err) log.Printf("http server shutdown: %v", err)
} }
} }
// runIncrementalSyncLoop periodically syncs new activities for every
// provisioned user in the background so the frontend doesn't need to
// trigger every sync manually.
func runIncrementalSyncLoop(ctx context.Context, server *api.Server, every time.Duration) {
ticker := time.NewTicker(every)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
server.RunIncrementalSyncForAllUsers(ctx)
}
}
}

View File

@@ -264,31 +264,6 @@ func (s *Server) removeGarminClient(userID int64) {
} }
} }
// RunIncrementalSyncForAllUsers is called on a timer (see main.go) to sync
// every provisioned user in turn, replacing the old single-global-Service
// background loop.
func (s *Server) RunIncrementalSyncForAllUsers(ctx context.Context) {
users, err := s.DB.ListUsers(ctx)
if err != nil {
log.Printf("api: list users for incremental sync: %v", err)
return
}
for _, u := range users {
svc, err := s.syncFor(ctx, u.ID)
if err != nil {
log.Printf("api: sync service for user %d: %v", u.ID, err)
continue
}
if err := svc.IncrementalSync(ctx); err != nil {
log.Printf("api: incremental sync for user %d: %v", u.ID, err)
continue
}
if err := svc.FillPendingDetails(ctx, 50); err != nil {
log.Printf("api: fill pending details for user %d: %v", u.ID, err)
}
}
}
var requestIDCounter atomic.Int64 var requestIDCounter atomic.Int64
// requestLoggingMiddleware logs one JSON line per HTTP request (method, // requestLoggingMiddleware logs one JSON line per HTTP request (method,

View File

@@ -36,7 +36,6 @@ type Config struct {
GarminTokenStoreRoot string GarminTokenStoreRoot string
MinConfidence float64 MinConfidence float64
IncrementalSyncEvery time.Duration
// LogLevel controls internal/applog's JSON logger ("debug"|"info"|"warn"|"error"). // LogLevel controls internal/applog's JSON logger ("debug"|"info"|"warn"|"error").
LogLevel string LogLevel string
@@ -76,7 +75,6 @@ func Load() (Config, error) {
GarminPythonPath: getEnvDefault("GARMIN_WRAPPER_PYTHON", "python3"), GarminPythonPath: getEnvDefault("GARMIN_WRAPPER_PYTHON", "python3"),
GarminTokenStoreRoot: os.Getenv("GARMIN_TOKENSTORE"), GarminTokenStoreRoot: os.Getenv("GARMIN_TOKENSTORE"),
MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6), MinConfidence: getEnvFloat("GENIUSRUN_MIN_CONFIDENCE", 0.6),
IncrementalSyncEvery: getEnvDuration("GENIUSRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour),
LogLevel: getEnvDefault("GENIUSRUN_LOG_LEVEL", "info"), LogLevel: getEnvDefault("GENIUSRUN_LOG_LEVEL", "info"),
BackendURL: strings.TrimRight(os.Getenv("GENIUSRUN_BACKEND_URL"), "/"), BackendURL: strings.TrimRight(os.Getenv("GENIUSRUN_BACKEND_URL"), "/"),
OIDCIssuerURL: os.Getenv("GENIUSRUN_OIDC_ISSUER_URL"), OIDCIssuerURL: os.Getenv("GENIUSRUN_OIDC_ISSUER_URL"),

View File

@@ -32,26 +32,6 @@ func (db *DB) GetUserBySub(ctx context.Context, oidcSub string) (User, bool, err
return u, true, nil return u, true, nil
} }
// ListUsers returns every provisioned user, for the background incremental
// sync loop to iterate.
func (db *DB) ListUsers(ctx context.Context) ([]User, error) {
rows, err := db.QueryContext(ctx, `SELECT id, oidc_sub, display_name, created_at FROM users ORDER BY id`)
if err != nil {
return nil, fmt.Errorf("list users: %w", err)
}
defer rows.Close()
users := []User{}
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.OIDCSub, &u.DisplayName, &u.CreatedAt); err != nil {
return nil, fmt.Errorf("scan user row: %w", err)
}
users = append(users, u)
}
return users, rows.Err()
}
// neverMatchRule is the placeholder every fresh install's rule-engine kinds // neverMatchRule is the placeholder every fresh install's rule-engine kinds
// start with -- every activity lands in needs_review until the user tunes // start with -- every activity lands in needs_review until the user tunes
// real rules. // real rules.