The cross-platform settings contract needs one typed store behind it before a
resolver, routes or a migration can exist. This adds that storage to both
user-store backends and holds them to identical behavior.
PostgreSQL gets user_setting_values with the scope CHECK constraints, the five
partial unique indexes that enforce one explicit value per identity, and the
covering indexes the one-query read path needs, plus user_setting_mutations for
mutation_id idempotency and the inert user_setting_migration_rejects audit
table. The per-user SQLite store gets the same shape minus user_id, since that
database is already user-scoped.
The UserStore interface grows the typed operations: read one explicit value at
one scope, collect every candidate row for a resolution request in a single
query, upsert with a revision increment, unset, and the idempotency receipt
operations. The resolution read deliberately returns unranked candidates so the
resolver can rank in Go — one query per request, never one per scope, which the
pgx query-count test pins.
Delete behavior is application-enforced. Neither backend can inherit it from
constraints: the SQLite store declares no foreign keys, and library, series and
device columns are not FK targets in Postgres either. Profile deletion cascades
to profile-anchored values while account scope survives, forgetting a device
clears its profile_device values alongside the legacy overrides, and the
library/series purges remove only what is scoped to that entity.
The shared conformance suite covers all of it, including the set-versus-unset
distinction for false, 0, "" and null, so a divergence between the two backends
fails a test rather than reaching a client.
Part of #376
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(userstore): batch allowed-library and profile lookups when listing
Listing profiles cost 1 + P queries: one allowed-libraries lookup fired
per profile row while the cursor was still open, which on Postgres also
checks out a second pooled connection mid-scan. The admin sessions
dashboard makes it worse, calling ListProfiles once per streaming user
just to resolve names. The SQLite store had the same pattern for both
profiles and collections, even though the Postgres collections path was
already written with array_agg to avoid exactly this.
Collect the rows first, then fetch the child lists in one batched query
and stitch them together in Go. No behaviour change, just fewer round
trips.
* fix(userstore): avoid sqlite batch variable limits
---------
Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
- Wire introdb marker fetching into playback and Jellyfin compat
- Persist and expose recap/preview markers alongside intro and credits
- Add new playback/profile settings for recap and preview behavior