package store import ( "context" "database/sql" "fmt" ) const ( SyncKindBackfill = "backfill" SyncKindIncremental = "incremental" // SyncKindFull is a manually-triggered "Sync now" pass: Backfill followed // by IncrementalSync 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 backfill or incremental sync attempt. 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() }