package projectinstance import ( "context" "errors" "github.com/jackc/pgx/v5" ) func (s *pgStore) GetProjectMeta(ctx context.Context, projectID int64) (string, string, int, int, error) { var lifecycle, activeKey string var selVer, planRev int err := s.pool.QueryRow(ctx, ` SELECT COALESCE(lifecycle_status, 'planning'), COALESCE(active_workflow_key, ''), COALESCE(workflow_selection_version, 1), COALESCE(workflow_plan_revision, 0) FROM projects WHERE id = $1 `, projectID).Scan(&lifecycle, &activeKey, &selVer, &planRev) if errors.Is(err, pgx.ErrNoRows) { return "", "", 0, 0, ErrProjectNotFound } return lifecycle, activeKey, selVer, planRev, err } func (s *pgStore) UpdateProjectAfterActivate(ctx context.Context, projectID int64, workflowKey string, expectedVersion int, activatedBy int64) error { tag, err := s.pool.Exec(ctx, ` UPDATE projects SET lifecycle_status = 'active', active_workflow_key = $2, workflow_selection_version = workflow_selection_version + 1, active_workflow_changed_at = now(), active_workflow_changed_by = $4, updated_at = now() WHERE id = $1 AND workflow_selection_version = $3 `, projectID, workflowKey, expectedVersion, activatedBy) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrConcurrentConflict } return nil } func (s *pgStore) BumpPlanRevision(ctx context.Context, projectID int64, expectedPlanRevision int) (int, error) { var newRev int err := s.pool.QueryRow(ctx, ` UPDATE projects SET workflow_plan_revision = workflow_plan_revision + 1, updated_at = now() WHERE id = $1 AND workflow_plan_revision = $2 RETURNING workflow_plan_revision `, projectID, expectedPlanRevision).Scan(&newRev) if errors.Is(err, pgx.ErrNoRows) { return 0, ErrPlanRevisionConflict } return newRev, err } func (s *pgStore) GetPlanningInstance(ctx context.Context, projectID int64) (*WorkflowInstance, error) { row := s.pool.QueryRow(ctx, ` SELECT id, project_id, workflow_version_id, instance_no, status, config_revision, plan_hash, config_snapshot, activated_by, activated_at, created_by, created_at, updated_at FROM project_workflow_instances WHERE project_id = $1 AND status = 'planning' ORDER BY id DESC LIMIT 1 `, projectID) inst, err := scanInstance(row) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return inst, err } func (s *pgStore) GetLiveInstance(ctx context.Context, projectID int64) (*WorkflowInstance, error) { row := s.pool.QueryRow(ctx, ` SELECT id, project_id, workflow_version_id, instance_no, status, config_revision, plan_hash, config_snapshot, activated_by, activated_at, created_by, created_at, updated_at FROM project_workflow_instances WHERE project_id = $1 AND status IN ('active', 'awaiting_stage_confirmation', 'termination_requested') ORDER BY id DESC LIMIT 1 `, projectID) inst, err := scanInstance(row) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return inst, err } func (s *pgStore) GetGoverningInstanceForAccess(ctx context.Context, projectID int64) (*WorkflowInstance, error) { live, err := s.GetLiveInstance(ctx, projectID) if err != nil { return nil, err } if live != nil { return live, nil } row := s.pool.QueryRow(ctx, ` SELECT id, project_id, workflow_version_id, instance_no, status, config_revision, plan_hash, config_snapshot, activated_by, activated_at, created_by, created_at, updated_at FROM project_workflow_instances WHERE project_id = $1 AND status IN ('completed', 'terminated') ORDER BY id DESC LIMIT 1 `, projectID) inst, err := scanInstance(row) if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return inst, err } func (s *pgStore) HasLiveInstance(ctx context.Context, projectID int64) (bool, error) { var ok bool err := s.pool.QueryRow(ctx, ` SELECT EXISTS ( SELECT 1 FROM project_workflow_instances WHERE project_id = $1 AND status IN ('active', 'awaiting_stage_confirmation', 'termination_requested') ) `, projectID).Scan(&ok) return ok, err }