Repoint the last legacy column readers onto canonical contract resolution (settings cutover task A4a): - access.Resolver and policy.ViewerResolver now resolve catalog.metadata_language through settingsresolve (profile scope -> contract default) via a shared access.PreferredMetadataLanguage helper, instead of reading user_profiles.preferred_metadata_language. Resolution is deliberately unconstrained: the policy input this preference feeds is the one a constraint would have to reference, which is circular — see the key's manifest notes. - playback start now resolves playback.audio_language canonically for the profile default instead of reading user_profiles.language, matching the catalog detail path. Series and library override handling is unchanged. - items.go needed no change: it already consumes the resolver-produced scope.PreferredMetadataLanguage. The legacy columns keep their values but are no longer read on these paths; a profile with only a column value now resolves to the contract default, and a stored canonical value wins. Tests pin both directions in access, policy (including scope parity, where the column is now a decoy), and the playback handler. Read cost is one batched store read per resolution, same as the profile-row read it replaces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
51 lines
1.9 KiB
Go
51 lines
1.9 KiB
Go
package access
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/settingscontract"
|
|
"github.com/Silo-Server/silo-server/internal/settingskeys"
|
|
"github.com/Silo-Server/silo-server/internal/settingsresolve"
|
|
"github.com/Silo-Server/silo-server/internal/userstore"
|
|
)
|
|
|
|
// PreferredMetadataLanguage resolves catalog.metadata_language canonically for
|
|
// one profile: the stored profile-scope value, else the contract default. The
|
|
// legacy user_profiles.preferred_metadata_language column is deliberately not
|
|
// consulted — it migrated to the canonical store, and reading both would let
|
|
// them disagree.
|
|
//
|
|
// Resolution is unconstrained on purpose. The manifest gives this key no
|
|
// constrained_by because the policy input that could constrain it
|
|
// (profile_preferred_metadata_language) is populated from this very
|
|
// preference; a constraint here would be circular. See the key's notes in
|
|
// contracts/settings/v1/manifest.json.
|
|
//
|
|
// A resolution failure degrades to "" — the contract default, meaning "inherit
|
|
// the library's metadata language" — rather than failing scope resolution: the
|
|
// language is a presentation preference, not an access boundary.
|
|
func PreferredMetadataLanguage(ctx context.Context, store userstore.UserStore, profileID string) string {
|
|
if store == nil || profileID == "" {
|
|
return ""
|
|
}
|
|
contract, err := settingscontract.Load()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
resolved, err := settingsresolve.New(contract).Resolve(ctx, store,
|
|
settingsresolve.Context{ProfileID: profileID},
|
|
[]string{settingskeys.CatalogMetadataLanguage}, nil)
|
|
if err != nil || len(resolved) == 0 {
|
|
return ""
|
|
}
|
|
// The contract default is null — "no preference" — which unmarshals to "",
|
|
// the same spelling the legacy column used for unset.
|
|
var language string
|
|
if json.Unmarshal(resolved[0].Value, &language) != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(language)
|
|
}
|