"""User-settings use cases: general preferences + scrobbling configuration. Settings rows are created lazily — a user who never saved anything reads clean defaults. Partial updates are merged against current values here, so the repository always persists the complete desired state. The scrobbler session key is encrypted before it touches the DB (never stored or returned in plain). """ import uuid from dataclasses import replace from app.domain.entities.settings import UserSettings from app.domain.ports import SubsonicCipher, UserSettingsRepository class UserSettingsService: def __init__(self, *, settings: UserSettingsRepository, cipher: SubsonicCipher) -> None: self._settings = settings # Same Fernet cipher used for the Subsonic app-password — reused here to # encrypt the scrobbler session key at rest (symmetric, recoverable). self._cipher = cipher async def get(self, user_id: uuid.UUID) -> UserSettings: return await self._settings.get(user_id) or UserSettings.defaults(user_id) async def update_general( self, user_id: uuid.UUID, *, theme: str | None, stream_quality: str | None ) -> UserSettings: current = await self.get(user_id) merged = replace( current, theme=theme if theme is not None else current.theme, stream_quality=stream_quality if stream_quality is not None else current.stream_quality, ) return await self._settings.upsert(merged) async def set_scrobbling( self, user_id: uuid.UUID, *, enabled: bool, provider: str | None, username: str | None, session_key: str | None, ) -> UserSettings: """Replace the scrobbling config. ``session_key`` is write-only: a new value is encrypted and stored; omitting it keeps the existing key (the client can't read it back to re-send it).""" current = await self.get(user_id) session_key_enc: str | None if session_key is not None: session_key_enc = self._cipher.encrypt(session_key) else: session_key_enc = current.scrobble_session_key_enc merged = replace( current, scrobble_enabled=enabled, scrobble_provider=provider, scrobble_username=username, scrobble_session_key_enc=session_key_enc, ) return await self._settings.upsert(merged)