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:
2026-06-21 07:52:22 +02:00
parent e050f6fa2d
commit eabce18e41
22 changed files with 3216 additions and 2302 deletions

View File

@@ -826,14 +826,32 @@ Source: [src/server/api/config.py](../src/server/api/config.py#L189-L247)
Prefix: `/api/nfo`
Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L1-L684)
Source: [src/server/api/nfo.py](../src/server/api/nfo.py)
These endpoints manage tvshow.nfo metadata files and associated media (poster, logo, fanart) for anime series. NFO files use Kodi/XBMC format and are scraped from TMDB API.
These endpoints manage tvshow.nfo metadata files for anime series. The
per-anime settings page (replacing the old "NFO Diagnostics" UI) lives
at `/api/anime/{key}/settings` — see section **7. Anime Settings
Endpoints** below.
**Prerequisites:**
- TMDB API key must be configured in settings
- NFO service returns 503 if API key not configured
- NFO endpoints return 503 if the API key is missing
### Anime Settings — New Endpoints
For the UI-driven settings page (renamed from NFO Diagnostics), the
following endpoints replace the older `/api/nfo/{serie_id}/*` flow:
| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/api/anime/{key}/settings` | Return all editable fields for a series |
| PUT | `/api/anime/{key}/settings` | Update name/folder/tmdb_id/tvdb_id/site, optionally regenerate tvshow.nfo |
| POST | `/api/anime/{key}/regenerate-nfo` | Regenerate tvshow.nfo using TMDB |
See [AnimeSettingsResponse](../src/server/models/anime.py) for the
response shape, and [section 7](#7-anime-settings-endpoints-new) for
full documentation.
### GET /api/nfo/{serie_id}/check
@@ -1594,3 +1612,109 @@ GET /api/anime?page=2&per_page=50
```
Source: [src/server/api/anime.py](../src/server/api/anime.py#L180-L220)
## 7. Anime Settings Endpoints (New)
Replaces the old "NFO Diagnostics" page with a per-anime settings UI
that views and edits anime metadata directly in the database.
Source: [src/server/api/anime.py](../src/server/api/anime.py)
### GET /api/anime/{anime_key}/settings
Return the full editable settings payload for a single anime series.
**Authentication:** Required
**Path Parameters:**
- `anime_key` (string): Series unique key (e.g., `attack-on-titan`)
**Response (200 OK):** [`AnimeSettingsResponse`](../src/server/models/anime.py)
```json
{
"key": "attack-on-titan",
"name": "Attack on Titan",
"site": "aniworld.to",
"folder": "Attack on Titan (2013)",
"year": 2013,
"tmdb_id": 1429,
"tvdb_id": 789,
"has_nfo": true,
"nfo_path": "/anime/Attack on Titan (2013)/tvshow.nfo",
"nfo_created_at": "2026-01-15T10:30:00+00:00",
"nfo_updated_at": "2026-01-15T10:30:00+00:00",
"loading_status": "completed",
"episode_count": 25,
"missing_episode_count": 0
}
```
**Errors:**
- `401 Unauthorized` — Not authenticated.
- `404 Not Found` — Series with the given key does not exist.
### PUT /api/anime/{anime_key}/settings
Update editable fields for a single anime series. Optional flags
control whether the on-disk folder is renamed and whether
`tvshow.nfo` is regenerated.
**Authentication:** Required
**Request Body** ([`AnimeSettingsUpdateRequest`](../src/server/models/anime.py)):
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | Display name (1500 chars) |
| `folder` | string | no | Filesystem folder name |
| `tmdb_id` | int | no | TMDB ID (positive integer, max 10 digits) |
| `tvdb_id` | int | no | TVDB ID (positive integer, max 10 digits) |
| `site` | string | no | Provider site URL |
| `apply_to_nfo` | bool | no | If true, regenerate `tvshow.nfo` with the new values (requires `tmdb_id`) |
| `rename_disk` | bool | no | If true and `folder` changed, rename the folder on disk |
**Example:**
```bash
curl -X PUT "http://127.0.0.1:8000/api/anime/attack-on-titan/settings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tmdb_id": 9999, "apply_to_nfo": true}'
```
**Response (200 OK):** Updated [`AnimeSettingsResponse`](../src/server/models/anime.py).
**Errors:**
- `400 Bad Request``apply_to_nfo=true` but the series has no `tmdb_id`.
- `401 Unauthorized` — Not authenticated.
- `404 Not Found` — Series with the given key does not exist.
- `422 Unprocessable Entity` — Validation failure (empty name, invalid
folder, non-positive `tmdb_id`/`tvdb_id`, oversized id, path traversal).
### POST /api/anime/{anime_key}/regenerate-nfo
Regenerate `tvshow.nfo` for a single anime using TMDB.
**Authentication:** Required
**Response (200 OK):** [`AnimeSettingsRegenerateNfoResponse`](../src/server/models/anime.py)
```json
{
"success": true,
"message": "NFO regenerated. 2 tags updated.",
"nfo_path": "/anime/Attack on Titan (2013)/tvshow.nfo",
"repaired_tags": ["title", "tmdbid"]
}
```
**Errors:**
- `400 Bad Request` — Series has no `tmdb_id`.
- `401 Unauthorized` — Not authenticated.
- `404 Not Found` — Series with the given key does not exist.
- `500 Internal Server Error` — TMDB or NFO regeneration failure.