feat(nfo): implement NFO diagnostics and repair
- Add NFO API endpoints: diagnostics, repair, validate, needs-repair - Create /settings/nfo page with full NFO management UI - Add NFO status section to edit modal with repair functionality - Add anime details API for edit modal pre-fill data - Fix auth test fixtures in test_nfo_diagnostics_repair.py Implements NFO diagnostics when editing anime series via right-click menu. Users can now check NFO status, see missing tags, and repair NFOs directly from the edit modal or the dedicated NFO settings page.
This commit is contained in:
@@ -133,21 +133,85 @@ class SearchResult(BaseModel):
|
||||
)
|
||||
)
|
||||
title: str = Field(..., description="Series title")
|
||||
folder: Optional[str] = Field(
|
||||
None,
|
||||
description=(
|
||||
"Series folder name on disk (metadata only) "
|
||||
"(e.g., 'Attack on Titan (2013)'). For display/filesystem ops only."
|
||||
)
|
||||
)
|
||||
snippet: Optional[str] = Field(None, description="Short description or snippet")
|
||||
thumbnail: Optional[HttpUrl] = Field(None, description="Thumbnail image URL")
|
||||
score: Optional[float] = Field(None, ge=0.0, le=1.0, description="Search relevance score (0-1)")
|
||||
|
||||
@field_validator('key', mode='before')
|
||||
@classmethod
|
||||
def normalize_key(cls, v: str) -> str:
|
||||
"""Normalize key to lowercase."""
|
||||
if isinstance(v, str):
|
||||
return v.lower().strip()
|
||||
return v
|
||||
|
||||
class AnimeDetailsResponse(BaseModel):
|
||||
"""Detailed response model for a single anime series with all metadata.
|
||||
|
||||
Used by the edit modal to pre-fill form fields with existing data.
|
||||
|
||||
Attributes:
|
||||
key: Unique series identifier
|
||||
name: Display name
|
||||
folder: Filesystem folder name
|
||||
year: Release year
|
||||
status: Show status (Continuing, Ended)
|
||||
plot: Plot description
|
||||
genres: List of genres
|
||||
studio: List of studios
|
||||
premiered: Premiere date
|
||||
rating: Rating value (0-10)
|
||||
rating_votes: Number of votes
|
||||
tmdb_id: TMDB ID
|
||||
tvdb_id: TVDB ID
|
||||
has_nfo: Whether NFO file exists
|
||||
nfo_created_at: NFO creation timestamp
|
||||
nfo_updated_at: NFO update timestamp
|
||||
"""
|
||||
|
||||
key: str = Field(..., description="Unique series identifier")
|
||||
name: str = Field(..., description="Display name")
|
||||
folder: Optional[str] = Field(None, description="Filesystem folder name")
|
||||
year: Optional[int] = Field(None, ge=1900, le=2100, description="Release year")
|
||||
status: Optional[str] = Field(None, description="Show status (Continuing, Ended)")
|
||||
plot: Optional[str] = Field(None, description="Plot description")
|
||||
genres: List[str] = Field(default_factory=list, description="List of genres")
|
||||
studio: List[str] = Field(default_factory=list, description="List of studios")
|
||||
premiered: Optional[str] = Field(None, description="Premiere date (YYYY-MM-DD)")
|
||||
rating: Optional[float] = Field(None, ge=0, le=10, description="Rating value (0-10)")
|
||||
rating_votes: Optional[int] = Field(None, ge=0, description="Number of votes")
|
||||
tmdb_id: Optional[int] = Field(None, description="TMDB ID")
|
||||
tvdb_id: Optional[int] = Field(None, description="TVDB ID")
|
||||
has_nfo: bool = Field(False, description="Whether NFO file exists")
|
||||
nfo_created_at: Optional[str] = Field(None, description="NFO creation timestamp")
|
||||
nfo_updated_at: Optional[str] = Field(None, description="NFO update timestamp")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"key": "attack-on-titan",
|
||||
"name": "Attack on Titan",
|
||||
"folder": "Attack on Titan (2013)",
|
||||
"year": 2013,
|
||||
"status": "Ended",
|
||||
"plot": "Humans fight against giant humanoid Titans.",
|
||||
"genres": ["Animation", "Action", "Drama"],
|
||||
"studio": ["Wit Studio", "MAPPA"],
|
||||
"premiered": "2013-04-07",
|
||||
"rating": 9.0,
|
||||
"rating_votes": 500000,
|
||||
"tmdb_id": 1429,
|
||||
"tvdb_id": 267440,
|
||||
"has_nfo": True,
|
||||
"nfo_created_at": "2025-01-15T10:30:00Z",
|
||||
"nfo_updated_at": "2025-01-15T10:30:00Z",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TMDBSearchResult(BaseModel):
|
||||
"""TMDB search result for auto-lookup functionality.
|
||||
|
||||
Attributes:
|
||||
tmdb_id: TMDB ID of the matched series
|
||||
title: Title from TMDB
|
||||
year: Release year
|
||||
overview: Short description
|
||||
vote_average: TMDB rating
|
||||
"""
|
||||
|
||||
tmdb_id: int = Field(..., description="TMDB ID")
|
||||
title: str = Field(..., description="Title from TMDB")
|
||||
year: Optional[int] = Field(None, description="Release year")
|
||||
overview: Optional[str] = Field(None, description="Short description")
|
||||
vote_average: Optional[float] = Field(None, description="TMDB rating")
|
||||
|
||||
Reference in New Issue
Block a user