From 6e49fade8b3a0bf24aa5b73fa4c24936a205897e Mon Sep 17 00:00:00 2001 From: Forbes Date: Sat, 14 Feb 2026 15:15:39 -0600 Subject: [PATCH] feat(db): add SettingsRepository for module state and config overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provides CRUD operations on the module_state and settings_overrides tables (created in migration 016). - GetModuleStates / SetModuleState — upsert module enabled/disabled - GetOverrides / SetOverride / DeleteOverride — JSONB config overrides Part of #99 --- internal/db/settings.go | 105 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 internal/db/settings.go diff --git a/internal/db/settings.go b/internal/db/settings.go new file mode 100644 index 0000000..b297774 --- /dev/null +++ b/internal/db/settings.go @@ -0,0 +1,105 @@ +package db + +import ( + "context" + "encoding/json" + "fmt" +) + +// SettingsRepository provides access to module_state and settings_overrides tables. +type SettingsRepository struct { + db *DB +} + +// NewSettingsRepository creates a new SettingsRepository. +func NewSettingsRepository(db *DB) *SettingsRepository { + return &SettingsRepository{db: db} +} + +// GetModuleStates returns all module enabled/disabled states from the database. +func (r *SettingsRepository) GetModuleStates(ctx context.Context) (map[string]bool, error) { + rows, err := r.db.pool.Query(ctx, + `SELECT module_id, enabled FROM module_state`) + if err != nil { + return nil, fmt.Errorf("querying module states: %w", err) + } + defer rows.Close() + + states := make(map[string]bool) + for rows.Next() { + var id string + var enabled bool + if err := rows.Scan(&id, &enabled); err != nil { + return nil, fmt.Errorf("scanning module state: %w", err) + } + states[id] = enabled + } + return states, rows.Err() +} + +// SetModuleState persists a module's enabled state. Uses upsert semantics. +func (r *SettingsRepository) SetModuleState(ctx context.Context, moduleID string, enabled bool, updatedBy string) error { + _, err := r.db.pool.Exec(ctx, + `INSERT INTO module_state (module_id, enabled, updated_by, updated_at) + VALUES ($1, $2, $3, now()) + ON CONFLICT (module_id) DO UPDATE + SET enabled = EXCLUDED.enabled, + updated_by = EXCLUDED.updated_by, + updated_at = now()`, + moduleID, enabled, updatedBy) + if err != nil { + return fmt.Errorf("setting module state: %w", err) + } + return nil +} + +// GetOverrides returns all settings overrides from the database. +func (r *SettingsRepository) GetOverrides(ctx context.Context) (map[string]json.RawMessage, error) { + rows, err := r.db.pool.Query(ctx, + `SELECT key, value FROM settings_overrides`) + if err != nil { + return nil, fmt.Errorf("querying settings overrides: %w", err) + } + defer rows.Close() + + overrides := make(map[string]json.RawMessage) + for rows.Next() { + var key string + var value json.RawMessage + if err := rows.Scan(&key, &value); err != nil { + return nil, fmt.Errorf("scanning settings override: %w", err) + } + overrides[key] = value + } + return overrides, rows.Err() +} + +// SetOverride persists a settings override. Uses upsert semantics. +func (r *SettingsRepository) SetOverride(ctx context.Context, key string, value any, updatedBy string) error { + jsonVal, err := json.Marshal(value) + if err != nil { + return fmt.Errorf("marshaling override value: %w", err) + } + _, err = r.db.pool.Exec(ctx, + `INSERT INTO settings_overrides (key, value, updated_by, updated_at) + VALUES ($1, $2, $3, now()) + ON CONFLICT (key) DO UPDATE + SET value = EXCLUDED.value, + updated_by = EXCLUDED.updated_by, + updated_at = now()`, + key, jsonVal, updatedBy) + if err != nil { + return fmt.Errorf("setting override: %w", err) + } + return nil +} + +// DeleteOverride removes a settings override. +func (r *SettingsRepository) DeleteOverride(ctx context.Context, key string) error { + _, err := r.db.pool.Exec(ctx, + `DELETE FROM settings_overrides WHERE key = $1`, key) + if err != nil { + return fmt.Errorf("deleting override: %w", err) + } + return nil +}