feat(api): scene hide + merge-duplicate endpoints for long-press actions

POST /scenes/{id}/hide — marks all playback_sources dead so the scene drops out
of has_playback lists (reversible via dead_at; row kept for dedup/refs).
POST /scenes/{keep_id}/merge/{drop_id} — merges drop into keep via scene_merge
(moves refs/performers/tags/fingerprints/playback). Backs the new tile long-press
menu (hide / mark-duplicate) replacing the dead animated-preview gesture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jtrzupek 2026-06-09 09:47:16 +02:00
parent abddd27856
commit e98ef6577e

View file

@ -831,6 +831,67 @@ def remove_performer_from_scene(
session.commit() session.commit()
class SceneHideOut(BaseModel):
scene_id: uuid.UUID
playback_marked_dead: int
@router.post("/{scene_id}/hide", response_model=SceneHideOut)
def hide_scene(
scene_id: uuid.UUID,
session: Annotated[Session, Depends(get_session)],
) -> SceneHideOut:
"""Ukryj scenę (user long-press → „usuń"). Oznacza wszystkie playback_sources
jako dead scena wypada z list (has_playback=false). Odwracalne w DB (dead_at).
Nie kasujemy wiersza sceny zachowujemy refs/dedup, tylko znika z UI."""
from datetime import UTC, datetime
from app.models.playback_source import PlaybackSource
if session.get(Scene, scene_id) is None:
raise HTTPException(status_code=404, detail="scene not found")
rows = session.execute(
select(PlaybackSource).where(
PlaybackSource.scene_id == scene_id,
PlaybackSource.dead_at.is_(None),
)
).scalars().all()
now = datetime.now(UTC)
for p in rows:
p.dead_at = now
p.dead_reason = "user hid scene (long-press)"
session.commit()
return SceneHideOut(scene_id=scene_id, playback_marked_dead=len(rows))
class SceneMergeOut(BaseModel):
keep_id: uuid.UUID
dropped_id: uuid.UUID
@router.post("/{keep_id}/merge/{drop_id}", response_model=SceneMergeOut)
def merge_duplicate_scene(
keep_id: uuid.UUID,
drop_id: uuid.UUID,
session: Annotated[Session, Depends(get_session)],
) -> SceneMergeOut:
"""Scal `drop_id` w `keep_id` (user long-press → „oznacz duplikat" → wybór drugiej
sceny). Przenosi refs/performers/tags/fingerprints/playback (scene_merge), kasuje
`drop`. keep = scena na której user trzyma (zostaje), drop = wskazany duplikat."""
from app.resolve.scene_merge import MergeError, merge_scenes
if keep_id == drop_id:
raise HTTPException(status_code=400, detail="keep_id == drop_id")
if session.get(Scene, keep_id) is None or session.get(Scene, drop_id) is None:
raise HTTPException(status_code=404, detail="scene not found")
try:
merge_scenes(session, keep_id=keep_id, drop_id=drop_id, resolved_by="user_long_press_duplicate")
except MergeError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
session.commit()
return SceneMergeOut(keep_id=keep_id, dropped_id=drop_id)
class EnrichTagsOut(BaseModel): class EnrichTagsOut(BaseModel):
scene_id: uuid.UUID scene_id: uuid.UUID
added: int added: int