feat(anime): rename NFO Diagnostics to Anime Settings + add edit endpoints
Replaces the read-only 'NFO Diagnostics' page with a full per-anime
Settings page reached from the right-click context menu on series cards.
Users can now view and edit key, name, folder, tmdb_id, tvdb_id and site
for each anime; changes are persisted to the DB and optionally written
back to the NFO file or used to regenerate it.
Backend
- Rename NfoDiagnosticsResponse -> NfoSettingsResponse,
NfoSeriesDiagnostics -> NfoSeriesSettings
- Rename get_nfo_diagnostics -> get_nfo_settings,
repair_nfo -> repair_nfo_settings
- Fix nfo.py bug: repair was calling non-existent
update_series_nfo_status(); now uses update_nfo_status() and an
explicit AnimeSeriesService.update(nfo_path=...)
- New endpoints on /api/anime/{key}:
GET /settings -> AnimeSettingsResponse
PUT /settings -> AnimeSettingsResponse
(body: name/folder/tmdb_id/tvdb_id/site,
options: apply_to_nfo, rename_disk)
POST /regenerate-nfo -> AnimeSettingsRegenerateNfoResponse
- New Pydantic models: AnimeSettingsResponse,
AnimeSettingsUpdateRequest, AnimeSettingsRegenerateNfoResponse
- /anime/settings page route; /settings/nfo now 301-redirects to it
Frontend
- New AniWorld.AnimeSettingsManager JS module (single-page form,
no tabs) with public API init/loadSeries/saveSettings/regenerateNfo/
validateField/populateForm/showSaveSuccess/showError
- New anime-settings.html template + anime-settings.css
- Right-click menu: data-action 'nfo-diagnostics' replaced by
'anime-settings' (label 'Anime Settings'), navigates to
/anime/settings?key=...
- Library 'Open NFO Diagnostics' link renamed to 'Open Anime Settings'
Bug fix
- context-menu click handler was calling hide() BEFORE building the
navigation URL, which cleared currentSeriesKey to null and produced
/anime/settings?key=null. Captures the key into a local const first.
Regression-locked by tests/frontend/unit/context_menu.test.js.
Tests
- 21 new pytest tests in tests/api/test_anime_settings_endpoints.py
(GET/PUT/regenerate-nfo, auth, validation, nfo-repair bug regression)
- tests/api/test_nfo_endpoints.py trimmed to 6 focused tests
- 31 new Vitest unit tests for AnimeSettingsManager
- 5 new Vitest unit tests for ContextMenu (incl. source-invariant
regression guard for the hide()-before-key bug)
- 5 new Playwright E2E tests covering right-click, direct nav,
legacy /settings/nfo redirect, and context-menu labels
- New vitest.config.js (environment: happy-dom)
Docs
- Docs/API.md: new section 'Anime Settings Endpoints'
- Docs/CHANGELOG.md: documents the rename and the context-menu bug fix
Verified
- pytest: 27/27 (21 new + 6 trimmed nfo)
- vitest: 36/36 (31 anime-settings + 5 context-menu)
- playwright e2e: 5/5
This commit is contained in:
@@ -196,7 +196,7 @@ class AnimeDetailsResponse(BaseModel):
|
||||
|
||||
class TMDBSearchResult(BaseModel):
|
||||
"""TMDB search result for auto-lookup functionality.
|
||||
|
||||
|
||||
Attributes:
|
||||
tmdb_id: TMDB ID of the matched series
|
||||
title: Title from TMDB
|
||||
@@ -210,3 +210,88 @@ class TMDBSearchResult(BaseModel):
|
||||
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")
|
||||
|
||||
|
||||
class AnimeSettingsResponse(BaseModel):
|
||||
"""Response payload for the Anime Settings page.
|
||||
|
||||
Surfaces every anime_series field that can be viewed or edited
|
||||
by the user. Used by GET /api/anime/{key}/settings and the
|
||||
PUT response.
|
||||
"""
|
||||
|
||||
key: str = Field(..., description="Series unique key (primary identifier)")
|
||||
name: str = Field(..., description="Series display name")
|
||||
site: str = Field(..., description="Provider site URL")
|
||||
folder: str = Field(..., description="Filesystem folder name")
|
||||
year: Optional[int] = Field(None, description="Release year")
|
||||
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 tvshow.nfo exists")
|
||||
nfo_path: Optional[str] = Field(None, description="Path to tvshow.nfo file")
|
||||
nfo_created_at: Optional[str] = Field(None, description="ISO timestamp when NFO created")
|
||||
nfo_updated_at: Optional[str] = Field(None, description="ISO timestamp when NFO updated")
|
||||
loading_status: Optional[str] = Field(
|
||||
None, description="Current loading status of the series"
|
||||
)
|
||||
episode_count: int = Field(0, description="Total number of episodes tracked")
|
||||
missing_episode_count: int = Field(0, description="Number of missing episodes")
|
||||
|
||||
|
||||
class AnimeSettingsUpdateRequest(BaseModel):
|
||||
"""Request payload for PUT /api/anime/{key}/settings.
|
||||
|
||||
All fields are optional. Only the fields that are provided will
|
||||
be updated. Field-level validation happens in the API endpoint
|
||||
(e.g. folder sanitization, TMDB ID format).
|
||||
"""
|
||||
|
||||
name: Optional[str] = Field(
|
||||
None,
|
||||
min_length=1,
|
||||
max_length=500,
|
||||
description="Series display name",
|
||||
)
|
||||
folder: Optional[str] = Field(
|
||||
None,
|
||||
min_length=1,
|
||||
max_length=1000,
|
||||
description="Filesystem folder name",
|
||||
)
|
||||
tmdb_id: Optional[int] = Field(
|
||||
None,
|
||||
ge=1,
|
||||
le=9999999999,
|
||||
description="TMDB ID (positive integer, max 10 digits)",
|
||||
)
|
||||
tvdb_id: Optional[int] = Field(
|
||||
None,
|
||||
ge=1,
|
||||
le=9999999999,
|
||||
description="TVDB ID (positive integer, max 10 digits)",
|
||||
)
|
||||
site: Optional[str] = Field(
|
||||
None,
|
||||
max_length=500,
|
||||
description="Provider site URL",
|
||||
)
|
||||
apply_to_nfo: bool = Field(
|
||||
False,
|
||||
description="If true, regenerate tvshow.nfo with the new values",
|
||||
)
|
||||
rename_disk: bool = Field(
|
||||
False,
|
||||
description="If true and folder changed, rename the folder on disk",
|
||||
)
|
||||
|
||||
|
||||
class AnimeSettingsRegenerateNfoResponse(BaseModel):
|
||||
"""Response payload for POST /api/anime/{key}/regenerate-nfo."""
|
||||
|
||||
success: bool = Field(..., description="Whether regeneration succeeded")
|
||||
message: str = Field(..., description="Human-readable result message")
|
||||
nfo_path: Optional[str] = Field(None, description="Path to regenerated NFO file")
|
||||
repaired_tags: List[str] = Field(
|
||||
default_factory=list,
|
||||
description="Tags that were missing before regeneration",
|
||||
)
|
||||
|
||||
@@ -357,8 +357,8 @@ class NFOMissingResponse(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class NfoDiagnosticsResponse(BaseModel):
|
||||
"""Response for NFO diagnostics showing missing required tags."""
|
||||
class NfoSettingsResponse(BaseModel):
|
||||
"""Response for NFO settings inspection showing missing required tags."""
|
||||
|
||||
has_nfo: bool = Field(..., description="Whether tvshow.nfo exists")
|
||||
nfo_path: Optional[str] = Field(None, description="Path to NFO file if exists")
|
||||
@@ -372,8 +372,8 @@ class NfoDiagnosticsResponse(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class NfoSeriesDiagnostics(BaseModel):
|
||||
"""Diagnostics for a single series in the needs-repair list."""
|
||||
class NfoSeriesSettings(BaseModel):
|
||||
"""Settings summary for a single series in the needs-repair list."""
|
||||
|
||||
key: str = Field(..., description="Series unique key")
|
||||
name: str = Field(..., description="Series display name")
|
||||
|
||||
Reference in New Issue
Block a user