package db import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) // UpdateCheckRow is the persisted result of the last update-available // check for an instance (WI-14). CHECK-ONLY bookkeeping. type UpdateCheckRow struct { InstanceID string AppID string Branch string InstalledBuildID string LatestBuildID string UpdateAvailable bool Error string CheckedAt time.Time } // UpsertUpdateCheck records the latest check result for an instance. func (db *DB) UpsertUpdateCheck(ctx context.Context, r UpdateCheckRow) error { _, err := db.pool.Exec(ctx, ` INSERT INTO update_checks (instance_id, app_id, branch, installed_buildid, latest_buildid, update_available, error, checked_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) ON CONFLICT (instance_id) DO UPDATE SET app_id = EXCLUDED.app_id, branch = EXCLUDED.branch, installed_buildid = EXCLUDED.installed_buildid, latest_buildid = EXCLUDED.latest_buildid, update_available = EXCLUDED.update_available, error = EXCLUDED.error, checked_at = NOW() `, r.InstanceID, r.AppID, r.Branch, r.InstalledBuildID, r.LatestBuildID, r.UpdateAvailable, r.Error) if err != nil { return fmt.Errorf("upsert update check: %w", err) } return nil } // GetUpdateCheck returns the last stored check for an instance, or // (nil, nil) when the instance has never been checked. func (db *DB) GetUpdateCheck(ctx context.Context, instanceID string) (*UpdateCheckRow, error) { row := db.pool.QueryRow(ctx, ` SELECT instance_id, app_id, branch, installed_buildid, latest_buildid, update_available, error, checked_at FROM update_checks WHERE instance_id = $1 `, instanceID) var r UpdateCheckRow err := row.Scan(&r.InstanceID, &r.AppID, &r.Branch, &r.InstalledBuildID, &r.LatestBuildID, &r.UpdateAvailable, &r.Error, &r.CheckedAt) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } if err != nil { return nil, fmt.Errorf("get update check: %w", err) } return &r, nil } // ListUpdateChecks returns every stored check, keyed for the dashboard's // "Update available" badges. func (db *DB) ListUpdateChecks(ctx context.Context) ([]UpdateCheckRow, error) { rows, err := db.pool.Query(ctx, ` SELECT instance_id, app_id, branch, installed_buildid, latest_buildid, update_available, error, checked_at FROM update_checks ORDER BY instance_id `) if err != nil { return nil, fmt.Errorf("list update checks: %w", err) } defer rows.Close() var out []UpdateCheckRow for rows.Next() { var r UpdateCheckRow if err := rows.Scan(&r.InstanceID, &r.AppID, &r.Branch, &r.InstalledBuildID, &r.LatestBuildID, &r.UpdateAvailable, &r.Error, &r.CheckedAt); err != nil { return nil, fmt.Errorf("scan update check: %w", err) } out = append(out, r) } return out, rows.Err() } // DeleteUpdateCheck removes an instance's row (instance deleted). func (db *DB) DeleteUpdateCheck(ctx context.Context, instanceID string) error { _, err := db.pool.Exec(ctx, `DELETE FROM update_checks WHERE instance_id = $1`, instanceID) if err != nil { return fmt.Errorf("delete update check: %w", err) } return nil }