a263272935
Implements previously-stubbed /api/v1 endpoints (hexagonal: ports -> repos
-> services -> routers wired in deps):
- playlists: GET /{id}/cover (serves stored cover, 404 when absent)
- settings: GET/PATCH /settings + GET/PUT /settings/scrobbling — lazy
per-user row, write-only Fernet-encrypted scrobble session key; adds
user_settings table + migration (chains off dc126696f5a6)
- storage: GET /duplicates, /broken, /missing-metadata + admin POST
/cleanup (arq cleanup_storage worker; reconciles local refs only,
guarded against a storage-outage mass delete)
- admin: GET /services, /sources, /settings + POST /reindex; PATCH
/settings and /sources/{source} return 501 (config is env-managed)
Adds NotSupportedError (-> HTTP 501). Integration tests for each surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
30 lines
1.3 KiB
Python
30 lines
1.3 KiB
Python
"""ORM model for per-user settings (general preferences + scrobbling)."""
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy import Boolean, ForeignKey, String
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.infrastructure.db.base import Base
|
|
from app.infrastructure.db.models.mixins import TimestampMixin
|
|
|
|
|
|
class UserSettingsModel(TimestampMixin, Base):
|
|
"""One row per user, created lazily on first save. The primary key *is* the
|
|
user id (a 1:1 extension of ``users``), so there's no separate surrogate id."""
|
|
|
|
__tablename__ = "user_settings"
|
|
|
|
user_id: Mapped[uuid.UUID] = mapped_column(
|
|
ForeignKey("users.id", ondelete="CASCADE"),
|
|
primary_key=True,
|
|
)
|
|
theme: Mapped[str] = mapped_column(String(16), default="system", nullable=False)
|
|
stream_quality: Mapped[str] = mapped_column(String(16), default="original", nullable=False)
|
|
scrobble_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
scrobble_provider: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
scrobble_username: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
# Fernet-encrypted scrobbler session key / token (see core.security). Never
|
|
# the plaintext — mirrors how the Subsonic app-password is stored.
|
|
scrobble_session_key_enc: Mapped[str | None] = mapped_column(String(512), nullable=True)
|