Files
geniusrun/backend/internal/store/syncruns.go
Christophe Vila 3a7f305221 docs(store): update stale SyncRun doc comment
Every run recorded today is SyncKindFull (the single-pass "Sync now"
action) -- "backfill"/"incremental" only ever appear in historical
rows predating that consolidation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 09:01:17 +02:00

98 lines
3.4 KiB
Go

package store
import (
"context"
"database/sql"
"fmt"
)
const (
// SyncKindFull is a manually-triggered "Sync now" pass: backfillCore
// followed by incrementalSyncCore followed by FillPendingDetails,
// recorded as one run so the reported activity count covers the whole
// action instead of only whichever stage happened to finish last.
SyncKindFull = "full"
SyncStatusRunning = "running"
SyncStatusSuccess = "success"
SyncStatusError = "error"
)
// SyncRun records one sync attempt. Every run recorded today is
// SyncKindFull (the "Sync now" action, covering backfill + incremental sync
// + detail/workout fill in a single pass) -- "backfill"/"incremental" only
// ever appear in historical rows predating that consolidation.
type SyncRun struct {
ID int64
Kind string
StartedAt string
FinishedAt *string
ActivitiesFetched int
Status string
ErrorMessage *string
}
// StartSyncRun records a new in-progress sync run for userID and returns its id.
func (db *DB) StartSyncRun(ctx context.Context, userID int64, kind string) (int64, error) {
res, err := db.ExecContext(ctx, `
INSERT INTO sync_runs (user_id, kind, started_at, status) VALUES (?, ?, datetime('now'), ?)`,
userID, kind, SyncStatusRunning)
if err != nil {
return 0, fmt.Errorf("start sync run for user %d: %w", userID, err)
}
return res.LastInsertId()
}
// FinishSyncRun marks a sync run (owned by userID) as finished, recording
// how many activities were fetched and whether it succeeded.
func (db *DB) FinishSyncRun(ctx context.Context, userID, id int64, activitiesFetched int, errMsg *string) error {
status := SyncStatusSuccess
if errMsg != nil {
status = SyncStatusError
}
_, err := db.ExecContext(ctx, `
UPDATE sync_runs SET finished_at = datetime('now'), activities_fetched = ?, status = ?, error_message = ?
WHERE id = ? AND user_id = ?`, activitiesFetched, status, errMsg, id, userID)
if err != nil {
return fmt.Errorf("finish sync run %d for user %d: %w", id, userID, err)
}
return nil
}
// LatestSyncRun returns userID's most recent sync run, if any.
func (db *DB) LatestSyncRun(ctx context.Context, userID int64) (SyncRun, bool, error) {
row := db.QueryRowContext(ctx, `
SELECT id, kind, started_at, finished_at, activities_fetched, status, error_message
FROM sync_runs WHERE user_id = ? ORDER BY id DESC LIMIT 1`, userID)
var r SyncRun
err := row.Scan(&r.ID, &r.Kind, &r.StartedAt, &r.FinishedAt, &r.ActivitiesFetched, &r.Status, &r.ErrorMessage)
if err == sql.ErrNoRows {
return SyncRun{}, false, nil
}
if err != nil {
return SyncRun{}, false, fmt.Errorf("latest sync run for user %d: %w", userID, err)
}
return r, true, nil
}
// ListSyncRuns returns userID's recent sync runs, newest first.
func (db *DB) ListSyncRuns(ctx context.Context, userID int64, limit int) ([]SyncRun, error) {
rows, err := db.QueryContext(ctx, `
SELECT id, kind, started_at, finished_at, activities_fetched, status, error_message
FROM sync_runs WHERE user_id = ? ORDER BY id DESC LIMIT ?`, userID, limit)
if err != nil {
return nil, fmt.Errorf("list sync runs for user %d: %w", userID, err)
}
defer rows.Close()
runs := []SyncRun{}
for rows.Next() {
var r SyncRun
if err := rows.Scan(&r.ID, &r.Kind, &r.StartedAt, &r.FinishedAt, &r.ActivitiesFetched, &r.Status, &r.ErrorMessage); err != nil {
return nil, fmt.Errorf("scan sync run row: %w", err)
}
runs = append(runs, r)
}
return runs, rows.Err()
}