Files
silo-server/internal/userdb/displayprefs.go
T
QuickandClaude Fable 5 440a75b189 refactor(jellycompat): give DisplayPreferences its own table
The Jellyfin DisplayPreferences blobs rode the legacy user_settings
key/value table under synthetic jellycompat:* keys, which forced the
legacy settings API to carry a prefix carve-out in its otherwise-closed
unknown-key gate. They are the compat subsystem's storage, not user
settings: the contract neither validates nor resolves them.

Move them to a dedicated jellycompat_displayprefs table in both
backends, keyed by (prefs id, client) per user, with the blob stored as
opaque text served back byte-for-byte (deliberately not jsonb, which
would re-serialize it). The data-copy migrations — per-user SQLite V16
and a paired SQL + Go goose migration for Postgres — are transactional
and harmless to re-run, and both drive their key parsing and row
classification from the new internal/jellycompat/displayprefs package
so the backends cannot diverge, following the internal/settingsmigrate
precedent. A jellycompat:* row that does not parse as a DisplayPrefs
key (only ever writable through the removed carve-out) is recorded in
user_setting_migration_rejects rather than silently deleted.

With the last non-settings tenant gone, the jellycompatSettingPrefix
carve-out is deleted: the legacy settings endpoints now refuse
jellycompat:* keys like any other unknown key and never surface them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 13:32:41 +00:00

43 lines
1.2 KiB
Go

package userdb
import (
"database/sql"
"fmt"
"time"
)
// GetJellycompatDisplayPrefs returns the stored DisplayPreferences blob for
// one (prefs id, client), or "" when nothing is stored.
func GetJellycompatDisplayPrefs(db *sql.DB, prefsID, client string) (string, error) {
var value string
err := db.QueryRow(
"SELECT value FROM jellycompat_displayprefs WHERE prefs_id = ? AND client = ?",
prefsID, client,
).Scan(&value)
if err == sql.ErrNoRows {
return "", nil
}
if err != nil {
return "", fmt.Errorf("getting display prefs %q/%q: %w", prefsID, client, err)
}
return value, nil
}
// SetJellycompatDisplayPrefs stores a DisplayPreferences blob verbatim,
// replacing any previous value for the same (prefs id, client).
func SetJellycompatDisplayPrefs(db *sql.DB, prefsID, client, value string) error {
updatedAt := time.Now().UTC().Format(time.RFC3339Nano)
_, err := db.Exec(
`INSERT INTO jellycompat_displayprefs (prefs_id, client, value, updated_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(prefs_id, client) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at`,
prefsID, client, value, updatedAt,
)
if err != nil {
return fmt.Errorf("setting display prefs %q/%q: %w", prefsID, client, err)
}
return nil
}