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:
@@ -1,6 +1,6 @@
|
||||
"""NFO Management API endpoints.
|
||||
|
||||
Provides endpoints for NFO diagnostics, repair, and validation for anime series.
|
||||
Provides endpoints for NFO settings, repair, and validation for anime series.
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
@@ -11,9 +11,9 @@ from pydantic import BaseModel
|
||||
|
||||
from src.config.settings import settings
|
||||
from src.server.models.nfo import (
|
||||
NfoDiagnosticsResponse,
|
||||
NfoRepairResponse,
|
||||
NfoSeriesDiagnostics,
|
||||
NfoSeriesSettings,
|
||||
NfoSettingsResponse,
|
||||
)
|
||||
from src.server.services.anime_service import AnimeService
|
||||
from src.server.utils.dependencies import get_anime_service, require_auth
|
||||
@@ -62,7 +62,7 @@ class NfoNeedsRepairListResponse(BaseModel):
|
||||
"""Response listing series that need NFO repair."""
|
||||
|
||||
total: int
|
||||
series: List[NfoSeriesDiagnostics]
|
||||
series: List[NfoSeriesSettings]
|
||||
|
||||
|
||||
def _get_nfo_path(folder: str) -> str:
|
||||
@@ -123,13 +123,13 @@ async def _get_series_data(
|
||||
return None
|
||||
|
||||
|
||||
@router.get("/{key}/diagnostics", response_model=NfoDiagnosticsResponse)
|
||||
async def get_nfo_diagnostics(
|
||||
@router.get("/{key}/diagnostics", response_model=NfoSettingsResponse)
|
||||
async def get_nfo_settings(
|
||||
key: str,
|
||||
_auth: dict = Depends(require_auth),
|
||||
anime_service: AnimeService = Depends(get_anime_service),
|
||||
) -> NfoDiagnosticsResponse:
|
||||
"""Get NFO diagnostics for a specific series.
|
||||
) -> NfoSettingsResponse:
|
||||
"""Get NFO settings inspection for a specific series.
|
||||
|
||||
Checks if tvshow.nfo exists in the series folder and validates
|
||||
that required tags are present.
|
||||
@@ -140,7 +140,7 @@ async def get_nfo_diagnostics(
|
||||
anime_service: AnimeService dependency
|
||||
|
||||
Returns:
|
||||
NfoDiagnosticsResponse with has_nfo, nfo_path, missing_tags, required_tags
|
||||
NfoSettingsResponse with has_nfo, nfo_path, missing_tags, required_tags
|
||||
|
||||
Raises:
|
||||
HTTPException 404: If series not found
|
||||
@@ -165,7 +165,7 @@ async def get_nfo_diagnostics(
|
||||
nfo_exists = os.path.isfile(nfo_path)
|
||||
|
||||
if not nfo_exists:
|
||||
return NfoDiagnosticsResponse(
|
||||
return NfoSettingsResponse(
|
||||
has_nfo=False,
|
||||
nfo_path=None,
|
||||
missing_tags=REQUIRED_TAGS.copy(),
|
||||
@@ -175,7 +175,7 @@ async def get_nfo_diagnostics(
|
||||
# Parse and check for missing tags
|
||||
_, missing = _parse_nfo_file(nfo_path)
|
||||
|
||||
return NfoDiagnosticsResponse(
|
||||
return NfoSettingsResponse(
|
||||
has_nfo=True,
|
||||
nfo_path=nfo_path,
|
||||
missing_tags=missing,
|
||||
@@ -184,7 +184,7 @@ async def get_nfo_diagnostics(
|
||||
|
||||
|
||||
@router.post("/{key}/repair", response_model=NfoRepairResponse)
|
||||
async def repair_nfo(
|
||||
async def repair_nfo_settings(
|
||||
key: str,
|
||||
_auth: dict = Depends(require_auth),
|
||||
anime_service: AnimeService = Depends(get_anime_service),
|
||||
@@ -332,12 +332,20 @@ async def _create_or_update_nfo(
|
||||
logger.info("Created/updated tvshow.nfo for %s at %s", key, nfo_path)
|
||||
|
||||
# Update series NFO status in DB
|
||||
await anime_service.update_series_nfo_status(
|
||||
await anime_service.update_nfo_status(
|
||||
key=key,
|
||||
has_nfo=True,
|
||||
nfo_path=nfo_path,
|
||||
)
|
||||
|
||||
# Also update nfo_path in DB (not part of update_nfo_status signature)
|
||||
from src.server.database.connection import get_db_session
|
||||
from src.server.database.service import AnimeSeriesService
|
||||
|
||||
async with get_db_session() as db:
|
||||
series = await AnimeSeriesService.get_by_key(db, key)
|
||||
if series is not None:
|
||||
await AnimeSeriesService.update(db, series.id, nfo_path=nfo_path)
|
||||
|
||||
# Return list of repaired tags (all tags that were missing before)
|
||||
return missing_before
|
||||
|
||||
@@ -411,7 +419,7 @@ async def get_series_needing_repair(
|
||||
NfoNeedsRepairListResponse with total count and list of series diagnostics
|
||||
"""
|
||||
all_series = await anime_service.list_series_with_filters()
|
||||
series_needing_repair: List[NfoSeriesDiagnostics] = []
|
||||
series_needing_repair: List[NfoSeriesSettings] = []
|
||||
|
||||
anime_dir = getattr(settings, "anime_directory", None)
|
||||
if not anime_dir:
|
||||
@@ -432,7 +440,7 @@ async def get_series_needing_repair(
|
||||
nfo_exists = os.path.isfile(nfo_path)
|
||||
|
||||
if not nfo_exists:
|
||||
series_needing_repair.append(NfoSeriesDiagnostics(
|
||||
series_needing_repair.append(NfoSeriesSettings(
|
||||
key=key,
|
||||
name=name,
|
||||
folder=folder,
|
||||
@@ -444,7 +452,7 @@ async def get_series_needing_repair(
|
||||
# Parse and check for missing tags
|
||||
_, missing = _parse_nfo_file(nfo_path)
|
||||
if missing:
|
||||
series_needing_repair.append(NfoSeriesDiagnostics(
|
||||
series_needing_repair.append(NfoSeriesSettings(
|
||||
key=key,
|
||||
name=name,
|
||||
folder=folder,
|
||||
|
||||
Reference in New Issue
Block a user