Public instance has no accounts, so all user state was GLOBAL in DB — new users saw/overwrote each other's (and Jan's) favorites, watched badges and blacklists (bug 2026-06-10). Add device_id (VARCHAR 64) to 9 state tables with composite PK (device_id, entity_id); app sends X-Device-Id header (get_device_id dep). All favorites/scene-favorites/blacklist/watch + scene&movie list/detail (is_favorite, watched, blacklist-hide) now filter by device. Existing rows backfilled to 'legacy-shared'; POST /me/adopt-legacy reassigns them to the caller once. Old clients (no header) map to legacy-shared so they keep working until OTA updates. Migration 0022: add col, backfill, composite PK. Verified on prod: 967 progress rows preserved, device isolation holds (new device sees none of legacy state). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
2.7 KiB
Python
88 lines
2.7 KiB
Python
"""Scene favorites — ulubione sceny (single-user, równolegle do /favorites/performers).
|
|
|
|
Endpointy:
|
|
GET /scene-favorites — lista ulubionych scen (pełen SceneOut)
|
|
POST /scene-favorites/{scene_id} — dodaj (idempotent)
|
|
DELETE /scene-favorites/{scene_id} — usuń
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.device import get_device_id
|
|
from app.api.scenes import _build_scene_out
|
|
from app.api.schemas import SceneOut
|
|
from app.auth import require_api_key
|
|
from app.db import get_session
|
|
from app.models.favorite_scene import FavoriteScene
|
|
from app.models.scene import Scene
|
|
|
|
router = APIRouter(
|
|
prefix="/scene-favorites",
|
|
tags=["scene-favorites"],
|
|
dependencies=[Depends(require_api_key)],
|
|
)
|
|
|
|
|
|
class SceneFavoriteListOut(BaseModel):
|
|
items: list[SceneOut]
|
|
total: int
|
|
|
|
|
|
class SceneFavoriteToggleOut(BaseModel):
|
|
scene_id: uuid.UUID
|
|
favorited: bool
|
|
|
|
|
|
@router.get("", response_model=SceneFavoriteListOut)
|
|
def list_scene_favorites(
|
|
session: Annotated[Session, Depends(get_session)],
|
|
device_id: Annotated[str, Depends(get_device_id)],
|
|
) -> SceneFavoriteListOut:
|
|
rows = (
|
|
session.execute(
|
|
select(Scene, FavoriteScene)
|
|
.join(FavoriteScene, FavoriteScene.scene_id == Scene.id)
|
|
.where(FavoriteScene.device_id == device_id)
|
|
.order_by(FavoriteScene.created_at.desc())
|
|
)
|
|
.all()
|
|
)
|
|
items = [_build_scene_out(session, scene, device_id=device_id) for scene, _ in rows]
|
|
return SceneFavoriteListOut(items=items, total=len(items))
|
|
|
|
|
|
@router.post(
|
|
"/{scene_id}",
|
|
response_model=SceneFavoriteToggleOut,
|
|
status_code=status.HTTP_201_CREATED,
|
|
)
|
|
def add_scene_favorite(
|
|
scene_id: uuid.UUID,
|
|
session: Annotated[Session, Depends(get_session)],
|
|
device_id: Annotated[str, Depends(get_device_id)],
|
|
) -> SceneFavoriteToggleOut:
|
|
scene = session.get(Scene, scene_id)
|
|
if scene is None:
|
|
raise HTTPException(status_code=404, detail="scene not found")
|
|
existing = session.get(FavoriteScene, (device_id, scene_id))
|
|
if existing is None:
|
|
session.add(FavoriteScene(device_id=device_id, scene_id=scene_id))
|
|
return SceneFavoriteToggleOut(scene_id=scene_id, favorited=True)
|
|
|
|
|
|
@router.delete("/{scene_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def remove_scene_favorite(
|
|
scene_id: uuid.UUID,
|
|
session: Annotated[Session, Depends(get_session)],
|
|
device_id: Annotated[str, Depends(get_device_id)],
|
|
) -> None:
|
|
fav = session.get(FavoriteScene, (device_id, scene_id))
|
|
if fav is not None:
|
|
session.delete(fav)
|