package postgres import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" "knowledge-governance/internal/governance" ) var ErrInvalidUISettings = errors.New("invalid UI settings") const uiSettingsColumns = `id,state,document_refresh_seconds,resource_refresh_seconds,stalled_after_seconds,finalizing_after_seconds,ingestion_concurrency,test_weknora_base_url,production_weknora_base_url,production_tenant_id,production_knowledge_base_id,updated_by,created_at,activated_at,retired_at,error_code` const getActiveUISettingsSQL = ` SELECT ` + uiSettingsColumns + ` FROM ui_setting_revisions WHERE state = 'active' ` const createUISettingsCandidateSQL = ` INSERT INTO ui_setting_revisions( id,state,document_refresh_seconds,resource_refresh_seconds, stalled_after_seconds,finalizing_after_seconds,ingestion_concurrency, test_weknora_base_url,production_weknora_base_url,production_tenant_id, production_knowledge_base_id,updated_by,created_at ) VALUES($1,'validated',$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING ` + uiSettingsColumns + ` ` const getUISettingsRevisionSQL = ` SELECT ` + uiSettingsColumns + ` FROM ui_setting_revisions WHERE id=$1 ` const lockUISettingsRevisionSQL = ` SELECT ` + uiSettingsColumns + ` FROM ui_setting_revisions WHERE id=$1 FOR UPDATE ` const retireActiveUISettingsSQL = ` UPDATE ui_setting_revisions SET state='retired',retired_at=$2 WHERE state='active' AND id<>$1 ` const activateUISettingsSQL = ` UPDATE ui_setting_revisions SET state='active',activated_at=$2,retired_at=NULL,error_code='' WHERE id=$1 AND state='validated' RETURNING ` + uiSettingsColumns + ` ` func (s *Store) GetActiveUISettings(ctx context.Context) (governance.UISettings, error) { return scanUISettings(s.db.QueryRow(ctx, getActiveUISettingsSQL)) } func (s *Store) CreateUISettingsCandidate(ctx context.Context, request governance.CreateUISettingsCandidateRequest) (governance.UISettings, error) { if !request.Valid() { return governance.UISettings{}, ErrInvalidUISettings } id, err := newOpaqueID("uis-") if err != nil { return governance.UISettings{}, fmt.Errorf("create UI settings id: %w", err) } return scanUISettings(s.db.QueryRow(ctx, createUISettingsCandidateSQL, id, request.DocumentRefreshSeconds, request.ResourceRefreshSeconds, request.StalledAfterSeconds, request.FinalizingAfterSeconds, request.IngestionConcurrency, request.TestWeKnoraBaseURL, request.ProductionWeKnoraBaseURL, request.ProductionTenantID, request.ProductionKnowledgeBaseID, request.UpdatedBy, request.Now.UTC())) } // GetUISettingsRevision reads one revision in any state. Activation uses it to // compare the incoming endpoint against the active one before letting an // engineer apply a change only an admin may author. func (s *Store) GetUISettingsRevision(ctx context.Context, revisionID string) (governance.UISettings, error) { if !validShortID(revisionID) { return governance.UISettings{}, ErrInvalidUISettings } return scanUISettings(s.db.QueryRow(ctx, getUISettingsRevisionSQL, revisionID)) } func (s *Store) ActivateUISettings(ctx context.Context, revisionID string, now time.Time) (governance.UISettings, error) { if !validShortID(revisionID) || now.IsZero() { return governance.UISettings{}, ErrInvalidUISettings } var tx pgx.Tx var err error ownsTransaction := false if existing, ok := s.db.(pgx.Tx); ok { tx = existing } else { beginner, ok := s.db.(interface { Begin(context.Context) (pgx.Tx, error) }) if !ok { return governance.UISettings{}, errors.New("store does not support transactions") } tx, err = beginner.Begin(ctx) if err != nil { return governance.UISettings{}, fmt.Errorf("begin UI settings activation: %w", err) } ownsTransaction = true defer func() { _ = tx.Rollback(ctx) }() } candidate, err := scanUISettings(tx.QueryRow(ctx, lockUISettingsRevisionSQL, revisionID)) if err != nil { return governance.UISettings{}, err } if candidate.State == "active" { if ownsTransaction { if err := tx.Commit(ctx); err != nil { return governance.UISettings{}, fmt.Errorf("commit UI settings activation: %w", err) } } return candidate, nil } if candidate.State != "validated" { return governance.UISettings{}, ErrInvalidUISettings } activatedAt := now.UTC() if _, err := tx.Exec(ctx, retireActiveUISettingsSQL, revisionID, activatedAt); err != nil { return governance.UISettings{}, fmt.Errorf("retire active UI settings: %w", err) } active, err := scanUISettings(tx.QueryRow(ctx, activateUISettingsSQL, revisionID, activatedAt)) if err != nil { return governance.UISettings{}, err } if ownsTransaction { if err := tx.Commit(ctx); err != nil { return governance.UISettings{}, fmt.Errorf("commit UI settings activation: %w", err) } } return active, nil } func scanUISettings(row interface{ Scan(...any) error }) (governance.UISettings, error) { var settings governance.UISettings if err := row.Scan(&settings.ID, &settings.State, &settings.DocumentRefreshSeconds, &settings.ResourceRefreshSeconds, &settings.StalledAfterSeconds, &settings.FinalizingAfterSeconds, &settings.IngestionConcurrency, &settings.TestWeKnoraBaseURL, &settings.ProductionWeKnoraBaseURL, &settings.ProductionTenantID, &settings.ProductionKnowledgeBaseID, &settings.UpdatedBy, &settings.CreatedAt, &settings.ActivatedAt, &settings.RetiredAt, &settings.ErrorCode); err != nil { return governance.UISettings{}, fmt.Errorf("scan UI settings: %w", err) } if settings.State != "validated" && settings.State != "active" && settings.State != "retired" && settings.State != "failed" { return governance.UISettings{}, errors.New("invalid UI settings state") } return settings, nil }