reparatie diverse bestanden
This commit is contained in:
@@ -43,8 +43,8 @@ Agents mogen NOOIT:
|
||||
- code buiten deze repository aanpassen
|
||||
- andere Podman containers aanpassen
|
||||
- systeemconfiguratie wijzigen
|
||||
- Agents mogen geen wijzigingen maken die het contract in contracts/API_GOLDEN.md breken.
|
||||
- Agents mogen geen refactors of verbeteringen uitvoeren die niet expliciet gevraagd zijn.
|
||||
- wijzigingen maken die het contract in contracts/API_GOLDEN.md breken
|
||||
- refactors of verbeteringen uitvoeren die niet expliciet gevraagd zijn
|
||||
|
||||
Agents mogen alleen werken **binnen deze repository**.
|
||||
|
||||
@@ -56,14 +56,14 @@ Agents mogen alleen werken **binnen deze repository**.
|
||||
De volgende bestanden zijn kritisch en mogen alleen aangepast worden na expliciete bevestiging van de gebruiker:
|
||||
Deze bestanden bevatten infrastructuur en API-contracten.
|
||||
|
||||
app/services/tvdb_auth_service.py
|
||||
container/Containerfile
|
||||
requirements.txt
|
||||
contracts/API_GOLDEN.md
|
||||
AGENTS.md
|
||||
CHANGE_POLICY.md
|
||||
SAFE_FILES.md
|
||||
docs/ARCHITECTURE.md
|
||||
- app/services/tvdb_auth_service.py
|
||||
- container/Containerfile
|
||||
- requirements.txt
|
||||
- contracts/API_GOLDEN.md
|
||||
- AGENTS.md
|
||||
- CHANGE_POLICY.md
|
||||
- SAFE_FILES.md
|
||||
- docs/ARCHITECTURE.md
|
||||
|
||||
---
|
||||
|
||||
@@ -118,6 +118,13 @@ Dit script valideert:
|
||||
|
||||
Als een regressietest faalt moet de wijziging worden teruggedraaid.
|
||||
|
||||
Test-URL policy:
|
||||
|
||||
- Op de host moet standaard getest worden via `http://127.0.0.1:8085`
|
||||
- In een sandbox of containerized AI-omgeving moet getest worden via `http://host.containers.internal:8085`
|
||||
- Testscripts moeten automatisch één van beide kunnen detecteren
|
||||
- Agents mogen niet aannemen dat `127.0.0.1:8085` vanuit de sandbox naar de host verwijst
|
||||
|
||||
---
|
||||
|
||||
# Feature Test Policy
|
||||
@@ -145,9 +152,7 @@ Regressietests mogen **geen nieuw TVDB-token forceren**.
|
||||
|
||||
De volgende tests moeten altijd slagen:
|
||||
|
||||
curl 127.0.0.1:8085/api/health
|
||||
curl 127.0.0.1:8085/api/tvdb/auth-status
|
||||
curl "127.0.0.1:8085/api/tvdb/search?q=elsbeth"
|
||||
./regression_tests.sh
|
||||
|
||||
Als een van deze tests faalt moet de wijziging worden teruggedraaid.
|
||||
|
||||
|
||||
+120
@@ -55,3 +55,123 @@ def search_series(q: str = Query(..., min_length=1)):
|
||||
return {"items": normalized}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
def _series_display_name(name: str | None, year: int | str | None) -> str | None:
|
||||
if name and year:
|
||||
return f"{name} ({year})"
|
||||
return name
|
||||
|
||||
|
||||
def _extract_episodes(payload: dict) -> list[dict]:
|
||||
data = payload.get("data")
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
if isinstance(data, dict):
|
||||
episodes = data.get("episodes")
|
||||
if isinstance(episodes, list):
|
||||
return episodes
|
||||
return []
|
||||
|
||||
|
||||
def _fetch_aired_episodes(client: TvdbClient, series_id: int) -> list[dict]:
|
||||
attempts = [
|
||||
(f"/series/{series_id}/episodes/default", {"seasonType": "aired"}),
|
||||
(f"/series/{series_id}/episodes/official", None),
|
||||
]
|
||||
errors = []
|
||||
|
||||
for path, params in attempts:
|
||||
try:
|
||||
payload = client.get(path, params=params)
|
||||
items = _extract_episodes(payload)
|
||||
if items:
|
||||
return items
|
||||
except Exception as exc:
|
||||
errors.append(f"{path}: {exc}")
|
||||
|
||||
raise RuntimeError(
|
||||
"Unable to load aired episodes from TVDB; tried known episode endpoints"
|
||||
if not errors
|
||||
else f"Unable to load aired episodes from TVDB ({'; '.join(errors)})"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/series/{series_id}/episodes")
|
||||
def get_series_episodes(
|
||||
series_id: int,
|
||||
order_type: str = Query("aired", min_length=1),
|
||||
):
|
||||
if order_type.lower() != "aired":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Unsupported order_type. Only 'aired' is supported in this MVP.",
|
||||
)
|
||||
|
||||
client = TvdbClient()
|
||||
|
||||
try:
|
||||
series_payload = client.get(f"/series/{series_id}")
|
||||
series_data = series_payload.get("data", {}) if isinstance(series_payload, dict) else {}
|
||||
series_name = (
|
||||
series_data.get("name")
|
||||
or series_data.get("seriesName")
|
||||
or series_data.get("slug")
|
||||
)
|
||||
series_year = series_data.get("year")
|
||||
|
||||
episodes = _fetch_aired_episodes(client, series_id)
|
||||
normalized = []
|
||||
|
||||
for item in episodes:
|
||||
season_number = (
|
||||
item.get("seasonNumber")
|
||||
or item.get("airedSeason")
|
||||
or item.get("season")
|
||||
or 0
|
||||
)
|
||||
episode_number = (
|
||||
item.get("number")
|
||||
or item.get("airedEpisodeNumber")
|
||||
or item.get("episodeNumber")
|
||||
or 0
|
||||
)
|
||||
title = item.get("name") or item.get("episodeName") or ""
|
||||
aired_on = item.get("aired") or item.get("firstAired")
|
||||
|
||||
label = None
|
||||
try:
|
||||
season_num = int(season_number)
|
||||
episode_num = int(episode_number)
|
||||
label = f"S{season_num:02}E{episode_num:02} - {title}"
|
||||
if aired_on:
|
||||
label = f"{label} - {aired_on}"
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
normalized.append(
|
||||
{
|
||||
"id": item.get("id"),
|
||||
"season_number": season_number,
|
||||
"episode_number": episode_number,
|
||||
"title": title,
|
||||
"aired": aired_on,
|
||||
"label": label,
|
||||
"raw": item,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"series": {
|
||||
"id": series_data.get("id") or series_id,
|
||||
"name": series_name,
|
||||
"year": series_year,
|
||||
"display_name": _series_display_name(series_name, series_year),
|
||||
},
|
||||
"order_type": "aired",
|
||||
"items": normalized,
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
+23
-9
@@ -1,12 +1,26 @@
|
||||
Ik heb een paar opmerkingen
|
||||
FROM python:3.12-slim
|
||||
|
||||
AGENTS.md
|
||||
Regression testst
|
||||
curl -X POST 127.0.0.1:8085/api/tvdb/login
|
||||
zorgt deze curl er niet continue voor dat er een nieuw token wordt aangevraagd en dat thetvdb mij dan gaat blokkeren?
|
||||
WORKDIR /app
|
||||
|
||||
ARCHITECTURE.md
|
||||
Ik wil de volumes voor de videobestanden graag als volgt. Dat komt overeen met de host en maakt het herkenbaarder
|
||||
# Install minimal system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
/Volumes/8TB/Shared_Folders/TV_Shows → /Volumes/8TB/Shared_Folders/TV_Shows
|
||||
/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows → /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows
|
||||
# Copy requirements
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy app
|
||||
COPY app /app/app
|
||||
|
||||
# Create runtime dirs
|
||||
RUN mkdir -p /app/data
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8085}"
|
||||
if [ -z "${BASE_URL:-}" ]; then
|
||||
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://127.0.0.1:8085"
|
||||
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://host.containers.internal:8085"
|
||||
else
|
||||
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${BASE_URL:-}" ]; then
|
||||
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://127.0.0.1:8085"
|
||||
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://host.containers.internal:8085"
|
||||
else
|
||||
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
SERIES_ID="${SERIES_ID:-430543}"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
echo "== Feature test 1: episodes endpoint returns series + items =="
|
||||
curl --fail --silent --show-error \
|
||||
"${BASE_URL}/api/tvdb/series/${SERIES_ID}/episodes?order_type=aired" \
|
||||
-o "${TMP_DIR}/episodes.json"
|
||||
|
||||
cat "${TMP_DIR}/episodes.json"
|
||||
|
||||
python3 - "${TMP_DIR}/episodes.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
assert isinstance(data, dict), "episodes response must be an object"
|
||||
assert "series" in data and isinstance(data["series"], dict), "episodes response missing series object"
|
||||
assert data.get("order_type") == "aired", "order_type must be aired"
|
||||
assert "items" in data and isinstance(data["items"], list), "episodes response missing items list"
|
||||
|
||||
series = data["series"]
|
||||
for key in ["id", "name", "year", "display_name"]:
|
||||
assert key in series, f"series missing key: {key}"
|
||||
|
||||
print("episodes endpoint JSON validation passed")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "== Feature test 2: unsupported order_type is rejected =="
|
||||
curl --silent --show-error \
|
||||
-o "${TMP_DIR}/episodes_bad_order.json" \
|
||||
-w "%{http_code}" \
|
||||
"${BASE_URL}/api/tvdb/series/${SERIES_ID}/episodes?order_type=absolute" \
|
||||
> "${TMP_DIR}/episodes_bad_order.status"
|
||||
|
||||
cat "${TMP_DIR}/episodes_bad_order.json"
|
||||
|
||||
python3 - "${TMP_DIR}/episodes_bad_order.status" "${TMP_DIR}/episodes_bad_order.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
status = Path(sys.argv[1]).read_text(encoding="utf-8").strip()
|
||||
data = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
|
||||
|
||||
assert status == "400", f"expected HTTP 400, got {status}"
|
||||
assert isinstance(data, dict), "error response must be an object"
|
||||
assert "detail" in data, "error response missing detail"
|
||||
|
||||
print("unsupported order_type validation passed")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "== Feature test 3: search output keeps year/display_name contract =="
|
||||
curl --fail --silent --show-error \
|
||||
"${BASE_URL}/api/tvdb/search?q=elsbeth" \
|
||||
-o "${TMP_DIR}/search.json"
|
||||
|
||||
cat "${TMP_DIR}/search.json"
|
||||
|
||||
python3 - "${TMP_DIR}/search.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
assert isinstance(data, dict), "search response must be an object"
|
||||
assert "items" in data and isinstance(data["items"], list), "search response missing items list"
|
||||
assert len(data["items"]) > 0, "search items list must not be empty"
|
||||
|
||||
first = data["items"][0]
|
||||
for key in ["id", "name", "year", "display_name"]:
|
||||
assert key in first, f"search item missing key: {key}"
|
||||
|
||||
print("search contract validation passed")
|
||||
PY
|
||||
|
||||
echo
|
||||
echo "All episodes feature tests passed."
|
||||
+10
-1
@@ -1,7 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:8085}"
|
||||
if [ -z "${BASE_URL:-}" ]; then
|
||||
if curl --silent --fail http://127.0.0.1:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://127.0.0.1:8085"
|
||||
elif curl --silent --fail http://host.containers.internal:8085/api/health >/dev/null 2>&1; then
|
||||
BASE_URL="http://host.containers.internal:8085"
|
||||
else
|
||||
echo "ERROR: could not determine BASE_URL. Tried 127.0.0.1 and host.containers.internal." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
|
||||
@@ -0,0 +1,639 @@
|
||||
# Rename-MVP — Definitieve Codex instructie en technisch ontwerp
|
||||
|
||||
## Doel
|
||||
|
||||
Bouw verder aan een minimalistische webapplicatie als uitgeklede webversie van **Rename My TV Series**.
|
||||
|
||||
De applicatie moet:
|
||||
|
||||
- draaien in een **rootless Podman container**
|
||||
- lokale mediabestanden kunnen hernoemen binnen expliciet toegestane videomappen
|
||||
- metadata ophalen via **TheTVDB v4 API**
|
||||
- een **4-panel interface** hebben
|
||||
- altijd eerst een **preview** tonen voordat bestanden echt worden hernoemd
|
||||
- veilig omgaan met paden, tokens en bestandsoperaties
|
||||
- de **serienaam inclusief jaartal** gebruiken in de UI en in de bestandsnaam
|
||||
|
||||
Dit project heeft al een **werkende baseline**:
|
||||
- container build werkt
|
||||
- FastAPI health endpoint werkt
|
||||
- TVDB login werkt
|
||||
- JWT parsing werkt
|
||||
- token persistence werkt
|
||||
- auth-status endpoint werkt
|
||||
- TVDB search werkt
|
||||
- `token_source` werkt
|
||||
|
||||
Belangrijk: dit project is nu **geen greenfield ontwerp meer**, maar een **bestaand werkend project** dat gecontroleerd verder moet worden uitgebouwd.
|
||||
|
||||
---
|
||||
|
||||
# 1. Technische context
|
||||
|
||||
## Hostomgeving
|
||||
|
||||
- Host OS: Debian Trixie
|
||||
- Podman version: 5.4.2
|
||||
- Project root: `/home/kodi/.config/rename-mvp`
|
||||
|
||||
## Runtime
|
||||
|
||||
- Rootless Podman
|
||||
- FastAPI backend
|
||||
- SQLite voor app-state en logging
|
||||
- JSON voor TVDB auth state
|
||||
- TheTVDB API v4
|
||||
- Lokale tests via `127.0.0.1:8085`
|
||||
|
||||
## Bestaande paden
|
||||
|
||||
### Auth state JSON
|
||||
- Host: `/home/kodi/.config/rename-mvp/data/tvdb_auth.json`
|
||||
- Container: `/app/data/tvdb_auth.json`
|
||||
|
||||
### Werkende testscripts
|
||||
- `/home/kodi/.config/rename-mvp/regression_tests.sh`
|
||||
- `/home/kodi/.config/rename-mvp/feature_test_template.sh`
|
||||
|
||||
### Governance-bestanden
|
||||
- `/home/kodi/.config/rename-mvp/AGENTS.md`
|
||||
- `/home/kodi/.config/rename-mvp/CHANGE_POLICY.md`
|
||||
- `/home/kodi/.config/rename-mvp/SAFE_FILES.md`
|
||||
- `/home/kodi/.config/rename-mvp/docs/ARCHITECTURE.md`
|
||||
- `/home/kodi/.config/rename-mvp/contracts/API_GOLDEN.md`
|
||||
|
||||
---
|
||||
|
||||
# 2. Niet-onderhandelbare regels
|
||||
|
||||
## 2.1 Geen contractbreuk
|
||||
|
||||
Je mag geen bestaande API endpoints of bestaande response-structuren breken.
|
||||
|
||||
Je mag:
|
||||
- nieuwe velden toevoegen
|
||||
- nieuwe endpoints toevoegen
|
||||
|
||||
Je mag niet:
|
||||
- bestaande veldnamen wijzigen
|
||||
- bestaande velden verwijderen
|
||||
- response-structuren wijzigen
|
||||
- bestaande endpoints hernoemen
|
||||
- bestaande endpoints verwijderen
|
||||
|
||||
`contracts/API_GOLDEN.md` is leidend.
|
||||
|
||||
## 2.2 Geen speculative improvements
|
||||
|
||||
Voer **geen refactors, herstructureringen of “verbeteringen” uit die niet expliciet nodig zijn voor de gevraagde wijziging**.
|
||||
|
||||
Als een wijziging niet strikt nodig is om het gevraagde doel te bereiken, doe die wijziging niet.
|
||||
|
||||
## 2.3 Safe files
|
||||
|
||||
De volgende bestanden zijn beschermd en mogen alleen gewijzigd worden als dat expliciet noodzakelijk is en als je het duidelijk benoemt:
|
||||
|
||||
- `app/services/tvdb_auth_service.py`
|
||||
- `container/Containerfile`
|
||||
- `requirements.txt`
|
||||
- `contracts/API_GOLDEN.md`
|
||||
- `AGENTS.md`
|
||||
- `CHANGE_POLICY.md`
|
||||
- `SAFE_FILES.md`
|
||||
- `docs/ARCHITECTURE.md`
|
||||
|
||||
## 2.4 Scopebeperking
|
||||
|
||||
Werk alleen binnen deze repository:
|
||||
|
||||
Pad op de host: `/home/kodi/.config/rename-mvp`
|
||||
Pad in deze codex omgeving: `/workspace/rename-mvp`
|
||||
|
||||
|
||||
Je mag niets aanpassen buiten deze projectscope.
|
||||
|
||||
## 2.5 Escalated execution
|
||||
|
||||
Escalated execution is toegestaan binnen deze projectscope voor:
|
||||
|
||||
- `podman build`
|
||||
- `podman run`
|
||||
- curl tests
|
||||
- lokale build- en testacties
|
||||
- bestanden aanmaken of aanpassen binnen dit project
|
||||
- dependency install binnen dit project
|
||||
|
||||
Niet toegestaan:
|
||||
|
||||
- systeembrede wijzigingen
|
||||
- firewallwijzigingen
|
||||
- system upgrades
|
||||
- andere repositories
|
||||
- andere stacks of containers aanpassen
|
||||
- destructieve cleanup acties buiten deze projectscope
|
||||
|
||||
---
|
||||
|
||||
# 3. Bestaande baseline die behouden moet blijven
|
||||
|
||||
Deze functionaliteit werkt al en mag niet stuk:
|
||||
|
||||
## 3.1 Health endpoint
|
||||
`GET /api/health`
|
||||
|
||||
Expected:
|
||||
```json
|
||||
{"status":"ok"}
|
||||
```
|
||||
|
||||
## 3.2 TVDB login endpoint
|
||||
`POST /api/tvdb/login`
|
||||
|
||||
Doet een expliciete forced login en vraagt een nieuw token aan.
|
||||
|
||||
Deze endpoint is **alleen voor debugging en handmatige auth-tests**, niet voor regressietests.
|
||||
|
||||
## 3.3 TVDB auth-status endpoint
|
||||
`GET /api/tvdb/auth-status`
|
||||
|
||||
Deze endpoint moet de actuele auth-status tonen inclusief:
|
||||
- configured
|
||||
- has_token
|
||||
- issued_at
|
||||
- expires_at
|
||||
- expires_at_unix
|
||||
- renew_after
|
||||
- renew_after_unix
|
||||
- is_expired
|
||||
- is_renewal_due
|
||||
- last_login_attempt_at
|
||||
- last_login_success_at
|
||||
- last_login_status
|
||||
- last_login_error
|
||||
- jwt_payload
|
||||
- token_source
|
||||
|
||||
## 3.4 TVDB search endpoint
|
||||
`GET /api/tvdb/search?q=elsbeth`
|
||||
|
||||
Deze endpoint werkt al en moet series teruggeven met ten minste:
|
||||
- id
|
||||
- name
|
||||
- year
|
||||
- display_name
|
||||
|
||||
De UI en bestandsnaamopbouw moeten het **jaartal van de serie** gebruiken.
|
||||
|
||||
## 3.5 Token lifecycle
|
||||
De TVDB bearer token is een JWT.
|
||||
Gebruik:
|
||||
- `exp` als primaire bron voor expiry
|
||||
- `iat` indien aanwezig
|
||||
- renew alleen wanneer nodig
|
||||
- niet onnodig opnieuw `/login` doen
|
||||
|
||||
De token mag **niet continu vernieuwd worden**.
|
||||
|
||||
## 3.6 token_source
|
||||
De auth-state gebruikt:
|
||||
- `none`
|
||||
- `login`
|
||||
- `cached`
|
||||
- `renewed`
|
||||
|
||||
Dit gedrag moet behouden blijven.
|
||||
|
||||
---
|
||||
|
||||
# 4. Functioneel doel van de applicatie
|
||||
|
||||
De uiteindelijke webapp moet een minimalistische 4-panel interface hebben.
|
||||
|
||||
## Paneel 1 — TVDB Search
|
||||
Functie:
|
||||
- zoekterm invoeren
|
||||
- zoeken via backend naar TheTVDB
|
||||
- resultaten tonen
|
||||
- gekozen serie details tonen
|
||||
|
||||
Zoekresultaten moeten minimaal tonen:
|
||||
- serienaam
|
||||
- jaar
|
||||
- first aired indien beschikbaar
|
||||
- network indien beschikbaar
|
||||
- poster indien beschikbaar
|
||||
|
||||
De series moeten in de UI worden weergegeven als bijvoorbeeld:
|
||||
|
||||
- `Elsbeth (2024)`
|
||||
- `The Office (2005)`
|
||||
|
||||
## Paneel 2 — Episodes
|
||||
Functie:
|
||||
- afleveringen van gekozen serie tonen
|
||||
- minimaal `Aired Order` ondersteunen
|
||||
- afleveringen per seizoen tonen
|
||||
- gebruiker kan willekeurige afleveringen selecteren
|
||||
|
||||
Afleveringen tonen bijvoorbeeld:
|
||||
- `S02E03 - Devil's Night - 2025-03-13`
|
||||
|
||||
## Paneel 3 — Selected Episodes
|
||||
Functie:
|
||||
- lijst van gekozen afleveringen voor de huidige sessie
|
||||
|
||||
Functionaliteit:
|
||||
- toevoegen
|
||||
- verwijderen
|
||||
- clear
|
||||
- reorder
|
||||
- move up / move down
|
||||
|
||||
## Paneel 4 — Selected Files
|
||||
Functie:
|
||||
- lijst van gekozen lokale videobestanden
|
||||
|
||||
Functionaliteit:
|
||||
- Add Files
|
||||
- Add Directory
|
||||
- Sort
|
||||
- Remove
|
||||
- Clear
|
||||
- Move Up / Move Down
|
||||
|
||||
## Workflow
|
||||
1. user zoekt serie
|
||||
2. user kiest serie
|
||||
3. backend haalt afleveringen op
|
||||
4. user voegt afleveringen toe aan selected episodes
|
||||
5. user voegt bestanden toe aan selected files
|
||||
6. backend maakt een 1-op-1 mapping op basis van volgorde
|
||||
7. backend maakt preview
|
||||
8. user bevestigt
|
||||
9. backend voert rename uit
|
||||
|
||||
---
|
||||
|
||||
# 5. Bestandsnaamopbouw
|
||||
|
||||
De standaard bestandsnaam moet zijn:
|
||||
|
||||
```text
|
||||
{series} ({year}) - S{season:02}E{episode:02} - {title}{ext}
|
||||
```
|
||||
|
||||
Voorbeeld:
|
||||
|
||||
```text
|
||||
Elsbeth (2024) - S02E03 - Devil's Night.mkv
|
||||
```
|
||||
|
||||
Belangrijk:
|
||||
- het **serienaam-jaartal** is verplicht onderdeel van de naam
|
||||
- het jaartal komt uit TheTVDB serie metadata
|
||||
- de UI moet dit jaartal ook tonen
|
||||
|
||||
De eerste MVP hoeft **airdate niet per se in de filename** te zetten zolang het afgesproken template hierboven intact blijft.
|
||||
|
||||
---
|
||||
|
||||
# 6. TVDB authenticatie en tokenbeheer
|
||||
|
||||
## 6.1 Authflow
|
||||
Gebruik de officiële TVDB v4 flow:
|
||||
- `POST /login`
|
||||
- body bevat `apikey`
|
||||
- `pin` alleen meesturen als aanwezig
|
||||
- response bevat bearer token
|
||||
|
||||
## 6.2 JWT verwerking
|
||||
De token is een JWT.
|
||||
|
||||
Verplicht:
|
||||
- decodeer payload
|
||||
- lees `exp`
|
||||
- lees `iat` indien aanwezig
|
||||
- gebruik `exp` als primaire expiry-bron
|
||||
|
||||
## 6.3 Opslag
|
||||
TVDB auth state wordt persistent opgeslagen in:
|
||||
|
||||
`/app/data/tvdb_auth.json`
|
||||
|
||||
Hostpad:
|
||||
|
||||
`/home/kodi/.config/rename-mvp/data/tvdb_auth.json`
|
||||
|
||||
In JSON mogen staan:
|
||||
- token
|
||||
- issued_at
|
||||
- expires_at
|
||||
- expires_at_unix
|
||||
- renew_after
|
||||
- renew_after_unix
|
||||
- last_login_attempt_at
|
||||
- last_login_success_at
|
||||
- last_login_status
|
||||
- last_login_error
|
||||
- jwt_payload
|
||||
- token_source
|
||||
|
||||
Niet in JSON:
|
||||
- `TVDB_API_KEY`
|
||||
- `TVDB_PIN`
|
||||
|
||||
Die moeten uit environment variables komen.
|
||||
|
||||
## 6.4 Renew-logica
|
||||
Renew alleen wanneer:
|
||||
- `now >= renew_after`
|
||||
- of TVDB `401` teruggeeft
|
||||
|
||||
Niet continu nieuwe tokens opvragen.
|
||||
|
||||
`POST /api/tvdb/login` is geen regressietest en geen normale flow; het is een debug endpoint.
|
||||
|
||||
---
|
||||
|
||||
# 7. Toegestane videopaden
|
||||
|
||||
De container moet dezelfde videopaden gebruiken als de host.
|
||||
|
||||
## Allowed media roots
|
||||
|
||||
- `/Volumes/8TB/Shared_Folders/TV_Shows`
|
||||
- `/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows`
|
||||
|
||||
## Volume mappings
|
||||
|
||||
- `/Volumes/8TB/Shared_Folders/TV_Shows -> /Volumes/8TB/Shared_Folders/TV_Shows`
|
||||
- `/Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows -> /Volumes/8TB_RAID1/Shared_Folders/Library/TV_Shows`
|
||||
- `/home/kodi/.config/rename-mvp/data -> /app/data`
|
||||
|
||||
De app mag alleen lezen/schrijven binnen een van de toegestane media roots.
|
||||
|
||||
---
|
||||
|
||||
# 8. Securitymodel
|
||||
|
||||
## Bestandspaden
|
||||
Nooit:
|
||||
- absolute clientpaden blind vertrouwen
|
||||
- `..` toestaan
|
||||
- buiten allowed media roots resolven
|
||||
|
||||
Altijd:
|
||||
- path validation doen
|
||||
- binnen allowlist van media roots blijven
|
||||
- alleen toegestane extensies accepteren
|
||||
- preview afdwingen vóór rename
|
||||
|
||||
## Allowed extensions MVP
|
||||
- `.mkv`
|
||||
- `.mp4`
|
||||
- `.avi`
|
||||
- `.m4v`
|
||||
- `.srt`
|
||||
|
||||
## Logging
|
||||
Nooit loggen:
|
||||
- API keys
|
||||
- bearer tokens
|
||||
- env secrets
|
||||
|
||||
---
|
||||
|
||||
# 9. Container en runtime
|
||||
|
||||
## Containerfile
|
||||
De bestaande werkende runtime moet behouden blijven.
|
||||
|
||||
## Rootless Podman
|
||||
De applicatie draait rootless.
|
||||
|
||||
## Lokale test-URL
|
||||
Gebruik voor runtime-tests de BASE_URL die bereikbaar is vanuit de huidige omgeving:
|
||||
|
||||
- op de host meestal: http://127.0.0.1:8085
|
||||
- in sandbox/container-omgevingen meestal: http://host.containers.internal:8085
|
||||
|
||||
Gebruik niet automatisch localhost.
|
||||
|
||||
De testscripts moeten BASE_URL automatisch detecteren of expliciet meegegeven krijgen.
|
||||
|
||||
---
|
||||
|
||||
# 10. Verwachte repositorystructuur
|
||||
|
||||
Houd deze structuur aan. Geen onnodige nieuwe mappen toevoegen.
|
||||
|
||||
```text
|
||||
/home/kodi/.config/rename-mvp
|
||||
├── AGENTS.md
|
||||
├── CHANGE_POLICY.md
|
||||
├── SAFE_FILES.md
|
||||
├── regression_tests.sh
|
||||
├── feature_test_template.sh
|
||||
├── requirements.txt
|
||||
├── .env
|
||||
├── .env.example
|
||||
├── README.md
|
||||
├── app/
|
||||
├── container/
|
||||
├── data/
|
||||
├── contracts/
|
||||
│ └── API_GOLDEN.md
|
||||
└── docs/
|
||||
└── ARCHITECTURE.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. API-contract dat behouden moet blijven
|
||||
|
||||
## 11.1 Health
|
||||
`GET /api/health`
|
||||
|
||||
Response:
|
||||
```json
|
||||
{"status":"ok"}
|
||||
```
|
||||
|
||||
## 11.2 TVDB Login
|
||||
`POST /api/tvdb/login`
|
||||
|
||||
Response bevat ten minste:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"issued_at": "...",
|
||||
"expires_at": "...",
|
||||
"renew_after": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## 11.3 TVDB Auth Status
|
||||
`GET /api/tvdb/auth-status`
|
||||
|
||||
Response bevat ten minste:
|
||||
```json
|
||||
{
|
||||
"configured": true,
|
||||
"has_token": true,
|
||||
"expires_at": "...",
|
||||
"renew_after": "...",
|
||||
"token_source": "cached"
|
||||
}
|
||||
```
|
||||
|
||||
Meer velden zijn toegestaan, minder niet.
|
||||
|
||||
## 11.4 TVDB Search
|
||||
`GET /api/tvdb/search?q=query`
|
||||
|
||||
Response bevat ten minste:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "...",
|
||||
"name": "...",
|
||||
"year": "...",
|
||||
"display_name": "..."
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 12. Testbeleid
|
||||
|
||||
## 12.1 Verplichte regressietests na elke wijziging
|
||||
Na **iedere wijziging** moet dit script worden uitgevoerd:
|
||||
|
||||
```bash
|
||||
./regression_tests.sh
|
||||
```
|
||||
|
||||
Dit script is verplicht.
|
||||
|
||||
Het valideert de bestaande baseline en mag geen nieuw TVDB-token forceren.
|
||||
|
||||
## 12.2 Verplichte feature tests
|
||||
Na iedere wijziging moet je daarnaast **drie tests uitvoeren voor de nieuwe of gewijzigde functionaliteit**.
|
||||
|
||||
Gebruik hiervoor als basis:
|
||||
|
||||
```bash
|
||||
./feature_test_template.sh
|
||||
```
|
||||
|
||||
Deze template moet per feature worden aangepast zodat de nieuwe functionaliteit echt en mechanisch getest wordt.
|
||||
|
||||
## 12.3 Geen forced login in regressie
|
||||
Verboden als standaard regressietest:
|
||||
|
||||
```bash
|
||||
curl -X POST 127.0.0.1:8085/api/tvdb/login
|
||||
```
|
||||
|
||||
Deze endpoint is alleen voor debugging.
|
||||
|
||||
---
|
||||
|
||||
# 13. Ontwikkelworkflow voor Codex
|
||||
|
||||
Bij iedere opdracht moet je dit proces volgen:
|
||||
|
||||
1. lees eerst de bestaande governance-bestanden:
|
||||
- `AGENTS.md`
|
||||
- `CHANGE_POLICY.md`
|
||||
- `SAFE_FILES.md`
|
||||
- `docs/ARCHITECTURE.md`
|
||||
- `contracts/API_GOLDEN.md`
|
||||
|
||||
2. vat kort samen wat je gaat wijzigen
|
||||
|
||||
3. benoem welke bestanden je wilt aanpassen
|
||||
|
||||
4. voer alleen de strikt noodzakelijke wijziging uit
|
||||
|
||||
5. draai verplichte regressietests:
|
||||
- `./regression_tests.sh`
|
||||
|
||||
6. draai drie feature tests voor de nieuwe functionaliteit
|
||||
|
||||
7. rapporteer:
|
||||
- welke bestanden zijn aangepast
|
||||
- welke regressietests zijn uitgevoerd
|
||||
- welke feature tests zijn uitgevoerd
|
||||
- welke risico’s of beperkingen er nog zijn
|
||||
|
||||
---
|
||||
|
||||
# 14. Eerste concrete opdracht aan Codex
|
||||
|
||||
Begin niet meteen met een brede refactor.
|
||||
|
||||
Begin met een gecontroleerde volgende stap.
|
||||
|
||||
## Eerste aanbevolen fase
|
||||
Bouw de volgende functionaliteit uit:
|
||||
|
||||
### Doel
|
||||
- endpoint om afleveringen van een gekozen TVDB serie op te halen
|
||||
- minimaal ondersteuning voor `aired order`
|
||||
- seriegegevens inclusief `year` behouden
|
||||
- output geschikt maken voor paneel 2 van de UI
|
||||
|
||||
### Verwachte richting
|
||||
Bijvoorbeeld iets als:
|
||||
|
||||
`GET /api/tvdb/series/{series_id}/episodes?order_type=aired`
|
||||
|
||||
Maar:
|
||||
- breek geen bestaande endpoints
|
||||
- wijzig geen bestaande auth-logica
|
||||
- wijzig geen container setup
|
||||
- gebruik bestaande TVDB token lifecycle
|
||||
- voeg tests toe
|
||||
|
||||
### Voor deze wijziging verplicht
|
||||
1. regressietests blijven slagen
|
||||
2. drie feature tests toevoegen/uitvoeren voor het nieuwe episodes endpoint
|
||||
3. output JSON valideren
|
||||
4. zoekresultaten en year-handling niet breken
|
||||
|
||||
---
|
||||
|
||||
# 15. Wat je uitdrukkelijk niet moet doen
|
||||
|
||||
- geen speculative refactors
|
||||
- geen auth-systeem herschrijven
|
||||
- geen token lifecycle vereenvoudigen
|
||||
- geen nieuwe frameworkkeuzes introduceren
|
||||
- geen projectstructuur wijzigen
|
||||
- geen governance-bestanden wijzigen tenzij expliciet nodig en gemeld
|
||||
- geen forced login gebruiken als regressietest
|
||||
- geen container- of pathmodel veranderen
|
||||
|
||||
---
|
||||
|
||||
# 16. Samenvatting voor Codex
|
||||
|
||||
Dit project heeft al een werkende baseline.
|
||||
|
||||
Jouw taak is niet om het project opnieuw te ontwerpen, maar om het gecontroleerd uit te bouwen.
|
||||
|
||||
Belangrijkste regels:
|
||||
- behoud werkende baseline
|
||||
- breek geen API contract
|
||||
- gebruik JWT `exp` als expiry-bron
|
||||
- renew token alleen wanneer nodig
|
||||
- gebruik seriejaar in UI en filename
|
||||
- werk alleen binnen allowed media roots
|
||||
- draai altijd `./regression_tests.sh`
|
||||
- draai altijd drie feature tests voor nieuwe functionaliteit
|
||||
- geen speculative improvements
|
||||
- escalated execution is toegestaan binnen alleen deze projectscope
|
||||
Reference in New Issue
Block a user