package sqlite import ( "context" "database/sql" "fmt" "inbox/internal/domain/lane" ) func (s *Store) CreateLane(ctx context.Context, value lane.Record) (lane.Record, error) { if err := value.Validate(); err != nil { return lane.Record{}, err } if value.ID == "" { id, err := s.newID("lane") if err != nil { return lane.Record{}, err } value.ID = id } now := s.now() value.CreatedAt = coalesceString(value.CreatedAt, now) value.UpdatedAt = now if _, err := s.db.ExecContext(ctx, ` INSERT INTO lanes( id, workspace_id, topic_id, name, slug, purpose, status, base_branch, branch_name, head_commit, worktree_path, container_name, runtime_endpoint, created_by_role_name, result_summary_markdown, error_message, created_at, updated_at, started_at, completed_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, value.ID, value.WorkspaceID, value.TopicID, value.Name, value.Slug, value.Purpose, string(value.Status), value.BaseBranch, value.BranchName, value.HeadCommit, value.WorktreePath, value.ContainerName, value.RuntimeEndpoint, value.CreatedByRoleName, value.ResultSummaryMarkdown, value.ErrorMessage, value.CreatedAt, value.UpdatedAt, nullableString(value.StartedAt), nullableString(value.CompletedAt), ); err != nil { return lane.Record{}, fmt.Errorf("create lane: %w", err) } return value, nil } func (s *Store) GetLane(ctx context.Context, laneID string) (lane.Record, error) { row := s.db.QueryRowContext(ctx, ` SELECT id, workspace_id, topic_id, name, slug, purpose, status, base_branch, branch_name, head_commit, worktree_path, container_name, runtime_endpoint, created_by_role_name, result_summary_markdown, error_message, created_at, updated_at, started_at, completed_at FROM lanes WHERE id = ? `, laneID) return scanLane(row) } func (s *Store) ListLanesByTopic(ctx context.Context, topicID string) ([]lane.Record, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, workspace_id, topic_id, name, slug, purpose, status, base_branch, branch_name, head_commit, worktree_path, container_name, runtime_endpoint, created_by_role_name, result_summary_markdown, error_message, created_at, updated_at, started_at, completed_at FROM lanes WHERE topic_id = ? ORDER BY created_at, id `, topicID) if err != nil { return nil, fmt.Errorf("list lanes by topic: %w", err) } defer rows.Close() var out []lane.Record for rows.Next() { item, err := scanLane(rows) if err != nil { return nil, err } out = append(out, item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate lanes by topic: %w", err) } return out, nil } func (s *Store) ListLanesByWorkspace(ctx context.Context, workspaceID string) ([]lane.Record, error) { rows, err := s.db.QueryContext(ctx, ` SELECT id, workspace_id, topic_id, name, slug, purpose, status, base_branch, branch_name, head_commit, worktree_path, container_name, runtime_endpoint, created_by_role_name, result_summary_markdown, error_message, created_at, updated_at, started_at, completed_at FROM lanes WHERE workspace_id = ? ORDER BY created_at, id `, workspaceID) if err != nil { return nil, fmt.Errorf("list lanes by workspace: %w", err) } defer rows.Close() var out []lane.Record for rows.Next() { item, err := scanLane(rows) if err != nil { return nil, err } out = append(out, item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate lanes by workspace: %w", err) } return out, nil } func (s *Store) UpdateLane(ctx context.Context, value lane.Record) (lane.Record, error) { if value.ID == "" { return lane.Record{}, fmt.Errorf("lane id is required") } before, err := s.GetLane(ctx, value.ID) if err != nil { return lane.Record{}, err } value.CreatedAt = before.CreatedAt value.UpdatedAt = s.now() if err := value.Validate(); err != nil { return lane.Record{}, err } if _, err := s.db.ExecContext(ctx, ` UPDATE lanes SET workspace_id = ?, topic_id = ?, name = ?, slug = ?, purpose = ?, status = ?, base_branch = ?, branch_name = ?, head_commit = ?, worktree_path = ?, container_name = ?, runtime_endpoint = ?, created_by_role_name = ?, result_summary_markdown = ?, error_message = ?, updated_at = ?, started_at = ?, completed_at = ? WHERE id = ? `, value.WorkspaceID, value.TopicID, value.Name, value.Slug, value.Purpose, string(value.Status), value.BaseBranch, value.BranchName, value.HeadCommit, value.WorktreePath, value.ContainerName, value.RuntimeEndpoint, value.CreatedByRoleName, value.ResultSummaryMarkdown, value.ErrorMessage, value.UpdatedAt, nullableString(value.StartedAt), nullableString(value.CompletedAt), value.ID, ); err != nil { return lane.Record{}, fmt.Errorf("update lane: %w", err) } return s.GetLane(ctx, value.ID) } func scanLane(s scanner) (lane.Record, error) { var item lane.Record var status string var startedAt sql.NullString var completedAt sql.NullString if err := s.Scan( &item.ID, &item.WorkspaceID, &item.TopicID, &item.Name, &item.Slug, &item.Purpose, &status, &item.BaseBranch, &item.BranchName, &item.HeadCommit, &item.WorktreePath, &item.ContainerName, &item.RuntimeEndpoint, &item.CreatedByRoleName, &item.ResultSummaryMarkdown, &item.ErrorMessage, &item.CreatedAt, &item.UpdatedAt, &startedAt, &completedAt, ); err != nil { return lane.Record{}, err } item.Status = lane.Status(status) if startedAt.Valid { item.StartedAt = startedAt.String } if completedAt.Valid { item.CompletedAt = completedAt.String } return item, nil }