Skip to content

refactor(settings): extract shared internal/core/settings store - #1529

Open
perber wants to merge 1 commit into
mainfrom
refactor/settings-store-consolidation
Open

refactor(settings): extract shared internal/core/settings store#1529
perber wants to merge 1 commit into
mainfrom
refactor/settings-store-consolidation

Conversation

@perber

@perber perber commented Sep 6, 2026

Copy link
Copy Markdown
Owner

What

Extracts the persistence machinery shared by branding.json and
public-access.json into a new internal/core/settings package, so the next
settings-managed JSON config (Hosted Onboarding Wizard's onboarding.json)
can be built on it from day one instead of as a fourth hand-rolled copy.

Each of the two subsystems carried its own copy of: data-dir file path, atomic
load/save, RWMutex-guarded in-memory cache, Reload() (called by
internal/restore after a snapshot swap), and — for public-access — the
env-managed vs settings-managed split.

New package internal/core/settings

  • Store[T] — atomic (shared.WriteFileAtomic), RWMutex-cached JSON
    file in the data dir. New / Get / Put / Reload. Missing file ⇒ the
    zero value passed to New, not an error. onLoad func(*T) hook re-applies
    non-persisted (json:"-") fields after every load/reload/put — replaces
    branding's hand-written constraint injection.
  • Reloadable (Reload() error) — what internal/restore iterates
    instead of enumerating each service by hand.
  • Value[T] / Managed[T] / Fixed[T] — the env-managed split.
    Managed[T]{Store: st} delegates to a *Store[T]; Fixed[T] is a
    boot-pinned value with no filePut returns the ErrEnvManaged
    sentinel, Reload is a no-op, so a Fixed entry is harmless in
    restore's []Reloadable.

Migrations

  • internal/brandingBrandingStore embeds *settings.Store[BrandingConfig];
    load/save/mutex/reload/constraint-injection deleted.
  • internal/publicaccessstore.go deleted; Service now carries a
    single settings.Value[fileConfig]. The (enabled, envManaged, store)
    triple, the local sync.RWMutex, the per-method mode branch, and the
    Service-level/Store-level double cache are gone. SetEnabled maps
    settings.ErrEnvManaged → the existing ErrCodeEnvManaged
    *LocalizedError, so the HTTP 409 behaviour is unchanged — every existing
    service_test.go case passes untouched.
  • internal/restoreConfig.BrandingService / Config.PublicAccess
    become Reloadables []settings.Reloadable; manager.go gains reloadAll()
    which accumulates every failure via errors.Join (matching
    reopenAllStores). cmd/leafwiki/main.go wires the slice.

Deliberately out of scope

internal/backup keeps its own ConfigStore (AES-GCM SecretBox, HKDF key
from the JWT secret). Store[T] has no codec hook; adding an opt-in
Store.WithCodec(enc, dec) later is the path if backup ever needs to share
the primitive.

Tests

  • New: store_test.go, value_test.go (round-trip, missing-file, perms,
    onLoad, Fixed/Managed semantics, Value in a []Reloadable, -race).
  • Full backend go test ./... green; go vet + golangci-lint v2 clean on
    the changed packages.

🤖 Generated with Claude Code

branding.json and public-access.json each carried their own copy of the
data-dir file path, atomic load/save, RWMutex-guarded cache, Reload() (for
the restore path), and — for public-access — the env-managed vs
settings-managed split.

Add internal/core/settings:

- Store[T]: atomic (shared.WriteFileAtomic), RWMutex-cached JSON file in the
  data dir. Missing file => the zero value passed to New, not an error. An
  onLoad(*T) hook re-applies non-persisted (json:"-") fields after every
  load/reload/put, replacing branding's hand-written constraint injection.
- Reloadable: what internal/restore iterates instead of enumerating each
  service by hand.
- Value[T] / Managed[T] / Fixed[T]: the env-managed split. Managed wraps a
  *Store[T]; Fixed is a boot-pinned value with no file whose Put returns
  ErrEnvManaged and whose Reload is a no-op, so it sits harmlessly in
  restore's []Reloadable.

Migrate internal/branding and internal/publicaccess onto it (publicaccess
loses its (enabled, envManaged, store) triple, its local mutex, the
per-method mode branch, and the Service-level/Store-level double cache;
SetEnabled maps settings.ErrEnvManaged onto the existing ErrCodeEnvManaged
LocalizedError so the HTTP 409 mapping is unchanged).

Rewire internal/restore: Config.BrandingService / Config.PublicAccess become
Reloadables []settings.Reloadable; manager gains reloadAll() which
accumulates every failure via errors.Join, matching reopenAllStores.

internal/backup keeps its own encrypted ConfigStore (AES-GCM SecretBox) and
is deliberately not migrated — Store[T] has no codec hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant