package store import ( "context" "database/sql" "fmt" "time" "provisioning/internal/model" ) type Operations struct { DB *sql.DB } func (s *Operations) Create(ctx context.Context, op model.Operation) (int64, error) { res, err := s.DB.ExecContext(ctx, ` INSERT INTO operations(host_id, kind, state, image_id) VALUES(?,?,?,?) `, op.HostID, op.Kind, model.OpActive, nullInt64(op.ImageID)) if err != nil { return 0, fmt.Errorf("insert operation: %w", err) } return res.LastInsertId() } func (s *Operations) Complete(ctx context.Context, id int64) error { _, err := s.DB.ExecContext(ctx, `UPDATE operations SET state = ?, completed_at = strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE id = ?`, model.OpCompleted, id) return err } func (s *Operations) Fail(ctx context.Context, id int64, errMsg string) error { _, err := s.DB.ExecContext(ctx, `UPDATE operations SET state = ?, completed_at = strftime('%Y-%m-%dT%H:%M:%SZ','now'), error_message = ? WHERE id = ?`, model.OpFailed, errMsg, id) return err } func (s *Operations) ListByHost(ctx context.Context, hostID int64) ([]model.Operation, error) { rows, err := s.DB.QueryContext(ctx, ` SELECT id, host_id, kind, state, COALESCE(image_id, 0), started_at, completed_at, COALESCE(error_message, '') FROM operations WHERE host_id = ? ORDER BY started_at DESC `, hostID) if err != nil { return nil, fmt.Errorf("list operations: %w", err) } defer rows.Close() var out []model.Operation for rows.Next() { var op model.Operation var startedAt string var completedAt sql.NullString if err := rows.Scan(&op.ID, &op.HostID, &op.Kind, &op.State, &op.ImageID, &startedAt, &completedAt, &op.ErrorMessage); err != nil { return nil, fmt.Errorf("scan operation: %w", err) } op.StartedAt, _ = time.Parse(time.RFC3339, startedAt) if completedAt.Valid { t, _ := time.Parse(time.RFC3339, completedAt.String) op.CompletedAt = &t } out = append(out, op) } return out, rows.Err() } func (s *Operations) GetActive(ctx context.Context, hostID int64) (*model.Operation, error) { row := s.DB.QueryRowContext(ctx, ` SELECT id, host_id, kind, state, COALESCE(image_id, 0), started_at, completed_at, COALESCE(error_message, '') FROM operations WHERE host_id = ? AND state = ? ORDER BY started_at DESC LIMIT 1 `, hostID, model.OpActive) var op model.Operation var startedAt string var completedAt sql.NullString if err := row.Scan(&op.ID, &op.HostID, &op.Kind, &op.State, &op.ImageID, &startedAt, &completedAt, &op.ErrorMessage); err != nil { if err == sql.ErrNoRows { return nil, ErrNotFound } return nil, fmt.Errorf("get active operation: %w", err) } op.StartedAt, _ = time.Parse(time.RFC3339, startedAt) return &op, nil } func nullInt64(v int64) any { if v == 0 { return nil } return v }