81 lines
3.2 KiB
Go
81 lines
3.2 KiB
Go
package sections
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"log/slog"
|
||
|
|
|
||
|
|
"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"
|
||
|
|
)
|
||
|
|
|
||
|
|
// legacySettingNextUpMode is the legacy account-wide user-settings key that
|
||
|
|
// stored the next-up presentation mode. It is read only as a fallback now: the
|
||
|
|
// setting moved to the profile-scoped canonical key ui.next_up_mode, and the
|
||
|
|
// legacy write endpoint no longer accepts this key.
|
||
|
|
const legacySettingNextUpMode = "next_up_mode"
|
||
|
|
|
||
|
|
// NextUpModeCombined keeps next-up episodes inside Continue Watching;
|
||
|
|
// NextUpModeSeparate gives them their own row. Combined is the contract
|
||
|
|
// default and what an absent value has always meant.
|
||
|
|
const (
|
||
|
|
NextUpModeCombined = "combined"
|
||
|
|
NextUpModeSeparate = "separate"
|
||
|
|
)
|
||
|
|
|
||
|
|
// NextUpMode resolves how the acting profile wants next-up episodes presented:
|
||
|
|
// the canonical profile-scoped ui.next_up_mode row, else the legacy
|
||
|
|
// account-wide next_up_mode setting, else "combined".
|
||
|
|
//
|
||
|
|
// The canonical row is what the web writes since the settings cutover — the
|
||
|
|
// legacy endpoint rejects the unregistered key, so an account-key read alone
|
||
|
|
// would silently ignore every edit made after the cutover. The legacy fallback
|
||
|
|
// stays because the one-time backfill only ran on stores that existed when it
|
||
|
|
// shipped: a store restored from a pre-backfill snapshot still carries the
|
||
|
|
// mode only in the account key. A stored canonical row always wins, so the
|
||
|
|
// fallback can never override a post-cutover edit.
|
||
|
|
func NextUpMode(ctx context.Context, store userstore.UserStore, profileID string) string {
|
||
|
|
if mode, ok := canonicalNextUpMode(ctx, store, profileID); ok {
|
||
|
|
return mode
|
||
|
|
}
|
||
|
|
mode, _ := store.GetSetting(ctx, legacySettingNextUpMode)
|
||
|
|
if mode == "" {
|
||
|
|
return NextUpModeCombined
|
||
|
|
}
|
||
|
|
return mode
|
||
|
|
}
|
||
|
|
|
||
|
|
// canonicalNextUpMode reads the profile-scoped canonical row. The second
|
||
|
|
// return reports whether a stored row decided the answer: a resolution that
|
||
|
|
// fell through to the contract default means "nothing stored", which is what
|
||
|
|
// lets the caller consult the legacy account key.
|
||
|
|
func canonicalNextUpMode(ctx context.Context, store userstore.UserStore, profileID string) (string, bool) {
|
||
|
|
if store == nil || profileID == "" {
|
||
|
|
return "", false
|
||
|
|
}
|
||
|
|
contract, err := settingscontract.Load()
|
||
|
|
if err != nil {
|
||
|
|
slog.WarnContext(ctx, "next-up mode resolution degraded to the legacy setting: loading settings contract failed",
|
||
|
|
"component", "sections", "profile_id", profileID, "error", err)
|
||
|
|
return "", false
|
||
|
|
}
|
||
|
|
resolved, err := settingsresolve.New(contract).Resolve(ctx, store,
|
||
|
|
settingsresolve.Context{ProfileID: profileID},
|
||
|
|
[]string{settingskeys.UiNextUpMode}, nil)
|
||
|
|
if err != nil {
|
||
|
|
slog.WarnContext(ctx, "next-up mode resolution degraded to the legacy setting: reading setting values failed",
|
||
|
|
"component", "sections", "profile_id", profileID, "error", err)
|
||
|
|
return "", false
|
||
|
|
}
|
||
|
|
if len(resolved) == 0 || resolved[0].Source == settingscontract.ScopeDefault {
|
||
|
|
return "", false
|
||
|
|
}
|
||
|
|
var mode string
|
||
|
|
if json.Unmarshal(resolved[0].Value, &mode) != nil || mode == "" {
|
||
|
|
return "", false
|
||
|
|
}
|
||
|
|
return mode, true
|
||
|
|
}
|