Files
kino-app/backend/models.py
T
2026-04-29 16:01:20 +00:00

215 lines
5.3 KiB
Python

from pydantic import BaseModel, Field, ConfigDict
from typing import List, Optional
from datetime import datetime, timezone
import uuid
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
# ---------- Users ----------
class UserCreate(BaseModel):
email: str
password: str
name: str
class UserLogin(BaseModel):
email: str
password: str
class UserPublic(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str
email: str
name: str
is_admin: bool = False
created_at: str
# ---------- Profiles ("Who's Watching") ----------
RATING_ORDER = ["G", "PG", "PG-13", "R", "NR"]
class ProfileCreate(BaseModel):
name: str
avatar_color: str = "#D9381E"
is_kids: bool = False
max_rating: str = "NR" # one of RATING_ORDER
class ProfileUpdate(BaseModel):
name: Optional[str] = None
avatar_color: Optional[str] = None
is_kids: Optional[bool] = None
max_rating: Optional[str] = None
class Profile(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str
name: str
avatar_color: str = "#D9381E"
is_kids: bool = False
max_rating: str = "NR"
created_at: str = Field(default_factory=_now_iso)
# ---------- Movies ----------
class MovieBase(BaseModel):
title: str
description: str = ""
year: int = 2024
duration_minutes: int = 0
rating: str = "NR"
genres: List[str] = []
cast: List[str] = []
director: str = ""
poster_url: str = ""
backdrop_url: str = ""
video_url: str = ""
storage_type: str = "external" # external | local | radarr
storage_path: Optional[str] = None
featured: bool = False
tmdb_id: Optional[int] = None
radarr_id: Optional[int] = None
hls_path: Optional[str] = None
hls_status: Optional[str] = None # pending|running|done|failed
class MovieCreate(MovieBase):
pass
class MovieUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
year: Optional[int] = None
duration_minutes: Optional[int] = None
rating: Optional[str] = None
genres: Optional[List[str]] = None
cast: Optional[List[str]] = None
director: Optional[str] = None
poster_url: Optional[str] = None
backdrop_url: Optional[str] = None
video_url: Optional[str] = None
storage_type: Optional[str] = None
storage_path: Optional[str] = None
featured: Optional[bool] = None
tmdb_id: Optional[int] = None
class Movie(MovieBase):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
created_at: str = Field(default_factory=_now_iso)
# ---------- Subtitles ----------
class Subtitle(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
movie_id: str
language: str = "en" # ISO code
label: str = "English"
storage_path: str # filename inside MEDIA_ROOT/subtitles
is_default: bool = False
created_at: str = Field(default_factory=_now_iso)
# ---------- Watchlist / Progress (now profile-scoped) ----------
class WatchlistItem(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
profile_id: str
user_id: str
movie_id: str
added_at: str = Field(default_factory=_now_iso)
class ProgressUpsert(BaseModel):
movie_id: str
position_seconds: float
duration_seconds: float
class Progress(BaseModel):
model_config = ConfigDict(extra="ignore")
profile_id: str
user_id: str
movie_id: str
position_seconds: float
duration_seconds: float
updated_at: str = Field(default_factory=_now_iso)
# ---------- Requests ----------
class RequestCreate(BaseModel):
title: str
year: Optional[int] = None
notes: str = ""
class RequestUpdate(BaseModel):
status: str
class MovieRequest(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str
user_name: str = ""
title: str
year: Optional[int] = None
notes: str = ""
status: str = "pending"
created_at: str = Field(default_factory=_now_iso)
# ---------- App Settings (admin) ----------
class AppSettings(BaseModel):
model_config = ConfigDict(extra="ignore")
tmdb_api_key: str = ""
radarr_url: str = ""
radarr_api_key: str = ""
class AppSettingsPublic(BaseModel):
"""Settings visible to admin UI (does not redact, since admin already has access)."""
tmdb_configured: bool = False
radarr_configured: bool = False
tmdb_api_key: str = ""
radarr_url: str = ""
radarr_api_key: str = ""
# ---------- Auth Tokens ----------
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserPublic
# ---------- TMDB / Radarr DTOs ----------
class TMDBSearchResult(BaseModel):
tmdb_id: int
title: str
year: Optional[int] = None
overview: str = ""
poster_url: str = ""
backdrop_url: str = ""
class RadarrMovieDTO(BaseModel):
radarr_id: int
title: str
year: Optional[int] = None
overview: str = ""
has_file: bool = False
file_path: Optional[str] = None
poster_url: str = ""
tmdb_id: Optional[int] = None