feat: add idempotent calendar bootstrap
Bootstrap independently reviewed catalogs through audited import, approval, and initial activation transitions, then resume insight scheduling safely.
This commit is contained in:
@@ -60,6 +60,26 @@ type Repository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func NewRepository(pool *pgxpool.Pool) *Repository { return &Repository{pool: pool} }
|
||||
|
||||
func (r *Repository) CatalogState(ctx context.Context, version string) (CatalogState, error) {
|
||||
return r.catalogState(ctx, `SELECT version,status,coverage_start_jalali_year,coverage_end_jalali_year,source_uri,source_version,source_checksum,imported_by FROM calendar_catalog_versions WHERE version=$1`, version)
|
||||
}
|
||||
|
||||
func (r *Repository) ActiveCatalogState(ctx context.Context) (CatalogState, error) {
|
||||
return r.catalogState(ctx, `SELECT version,status,coverage_start_jalali_year,coverage_end_jalali_year,source_uri,source_version,source_checksum,imported_by FROM calendar_catalog_versions WHERE status='ACTIVE'`)
|
||||
}
|
||||
|
||||
func (r *Repository) catalogState(ctx context.Context, query string, arguments ...any) (CatalogState, error) {
|
||||
var state CatalogState
|
||||
err := r.pool.QueryRow(ctx, query, arguments...).Scan(&state.Version, &state.Status, &state.CoverageStartJalaliYear, &state.CoverageEndJalaliYear, &state.SourceURI, &state.SourceVersion, &state.SourceChecksum, &state.ImportedBy)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return CatalogState{}, ErrCatalogNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return CatalogState{}, fmt.Errorf("querying calendar catalog state: %w", err)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ImportDraft(ctx context.Context, input Import) error {
|
||||
if err := ValidateEvents(input.Events); err != nil {
|
||||
return err
|
||||
@@ -169,3 +189,43 @@ func (r *Repository) Activate(ctx context.Context, version, actor string) error
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *Repository) ActivateInitial(ctx context.Context, version, actor string) error {
|
||||
if strings.TrimSpace(actor) == "" {
|
||||
return errors.New("event catalog: activation operator is required")
|
||||
}
|
||||
tx, err := r.pool.BeginTx(ctx, pgx.TxOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting initial calendar activation: %w", err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback(context.WithoutCancel(ctx)) }()
|
||||
if _, err := tx.Exec(ctx, `SELECT pg_advisory_xact_lock(hashtextextended('calendar-catalog-activation',0))`); err != nil {
|
||||
return fmt.Errorf("locking initial calendar activation: %w", err)
|
||||
}
|
||||
var activeVersion string
|
||||
err = tx.QueryRow(ctx, `SELECT version FROM calendar_catalog_versions WHERE status='ACTIVE'`).Scan(&activeVersion)
|
||||
if err == nil {
|
||||
if activeVersion == version {
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
return fmt.Errorf("%w: active version %q differs from requested version %q", ErrCatalogConflict, activeVersion, version)
|
||||
}
|
||||
if !errors.Is(err, pgx.ErrNoRows) {
|
||||
return fmt.Errorf("loading active calendar catalog: %w", err)
|
||||
}
|
||||
var id int64
|
||||
var sourceChecksum string
|
||||
if err := tx.QueryRow(ctx, `SELECT id,source_checksum FROM calendar_catalog_versions WHERE version=$1 AND status='APPROVED' FOR UPDATE`, version).Scan(&id, &sourceChecksum); err != nil {
|
||||
return fmt.Errorf("loading approved calendar catalog: %w", err)
|
||||
}
|
||||
if err := ValidateReviewableChecksum(sourceChecksum); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `UPDATE calendar_catalog_versions SET status='ACTIVE',activated_at=now() WHERE id=$1`, id); err != nil {
|
||||
return fmt.Errorf("activating calendar catalog: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO calendar_catalog_audit(catalog_version_id,action,actor) VALUES($1,'ACTIVATED',$2)`, id, actor); err != nil {
|
||||
return fmt.Errorf("auditing calendar activation: %w", err)
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user