goon/app/api/schemas.py
jtrzupek 6eb7cdd320 feat(movies): watched/continue-watching tracking end-to-end
Bug-report b207ff17 2026-05-26 ("przydaloby sie oznaczenie filmow juz
obejrzanych" - sceny mialy watched badge + dim, filmom brakowalo).

Backend:
- alembic 0018_movie_play_progress: nowa tabela (mirror scene_play_progress)
- MoviePlayProgress SQLAlchemy model
- MovieOut schema dolane finished/position_sec/last_played_at
- POST+DELETE /movies/{id}/progress endpointy (upsert via pg ON CONFLICT)
- _movie_to_out wstrzykuje progress z DB

Mobile:
- RouteParams.entityKind: 'scene'|'movie' (default scene dla back-compat)
- PlayerScreen NativeVideoPlayer + EmbedWebViewPlayer dispatchuja
  upsertProgress vs upsertMovieProgress po entityKind
- MovieDetailScreen przekazuje entityKind='movie' do nav
- MoviePosterCard renderuje dim + check badge + progress bar
  (parity ze ScenesScreen pattern)

Wczesniej MovieDetail przekazywal movieId jako sceneId -> backend
/scenes/<movieId>/progress zwracal 404 (silently caught). Po dodaniu
dedykowanego movie endpoint proper routing dziala.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:24:06 +02:00

132 lines
3.7 KiB
Python

"""Pydantic schemas eksportowane przez API."""
from __future__ import annotations
import uuid
from datetime import date, datetime
from pydantic import BaseModel, ConfigDict
class StudioOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
name: str
slug: str
network: str | None = None
class PerformerOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
canonical_name: str
slug: str
gender: str | None = None
as_alias: str | None = None
class TagOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
name: str
slug: str
class ExternalRefOut(BaseModel):
source: str
external_id: str
url: str | None = None
last_seen: datetime | None = None
class PlaybackSourceOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
origin: str
page_url: str
embed_url: str | None = None
stream_url: str | None = None
quality: str | None = None
duration_sec: int | None = None
thumbnail_url: str | None = None
animated_thumbnail_url: str | None = None
class SceneOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
title: str
slug: str | None = None
release_date: date | None = None
duration_sec: int | None = None
description: str | None = None
code: str | None = None
director: str | None = None
studio: StudioOut | None = None
performers: list[PerformerOut] = []
tags: list[TagOut] = []
external_refs: list[ExternalRefOut] = []
playback_sources: list[PlaybackSourceOut] = []
# Kiedy scena trafiła do bazy (ingest). Używane przez mobile do oznaczenia
# "NEW" na karcie scen w PerformerScenesScreen / StudioScenesScreen — gdy
# `created_at > last_seen_at` (favorite) → badge.
created_at: datetime | None = None
# Watched indicator (z `scene_play_progress`): mobile dim'uje kafelek gdy
# `finished=True`, pokazuje progress bar gdy `position_sec > 0`.
last_played_at: datetime | None = None
finished: bool = False
position_sec: int = 0
is_favorite: bool = False
class SceneListOut(BaseModel):
items: list[SceneOut]
total: int
page: int
per_page: int
class MovieChapterOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
chapter_index: int
title: str | None = None
start_sec: int | None = None
end_sec: int | None = None
scene_id: uuid.UUID | None = None
class MovieOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: uuid.UUID
title: str
slug: str | None = None
release_year: int | None = None
release_date: date | None = None
duration_sec: int | None = None
description: str | None = None
director: str | None = None
country: str | None = None
rating: float | None = None
poster_url: str | None = None
backdrop_url: str | None = None
studio: StudioOut | None = None
performers: list[PerformerOut] = []
tags: list[TagOut] = []
chapters: list[MovieChapterOut] = []
external_refs: list[ExternalRefOut] = []
playback_sources: list[PlaybackSourceOut] = []
# Used by mobile MoviesScreen NEW badge (created_at > client-stored seenSince)
# and MovieDetail favorite star.
created_at: datetime | None = None
is_favorite: bool = False
# Watched / continue-watching state (mirror SceneOut, bug-report b207ff17
# 2026-05-26 "przydałoby się oznaczenie filmów już obejrzanych").
last_played_at: datetime | None = None
finished: bool = False
position_sec: int = 0
class MovieListOut(BaseModel):
items: list[MovieOut]
total: int
page: int
per_page: int