from fastapi import APIRouter, Depends, Form, Request from fastapi.responses import RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy import select from app.db import get_db from app.models import GenreCandidate, GenreRun from app.security.deps import require_auth from app.services import genre_review_service router = APIRouter(prefix="/genres", tags=["genres"]) templates = Jinja2Templates(directory="app/templates") @router.get("") async def genres_index(request: Request, user: dict = Depends(require_auth), db=Depends(get_db)): latest_run = db.execute(select(GenreRun).order_by(GenreRun.id.desc())).scalars().first() candidates = [] if latest_run is not None: candidates = list( db.execute( select(GenreCandidate).where(GenreCandidate.genre_run_id == latest_run.id) ).scalars() ) return templates.TemplateResponse( request, "genres/index.html", {"latest_run": latest_run, "candidates": candidates} ) @router.post("/scan") async def scan(user: dict = Depends(require_auth)): # Always force=True: matches the weekly scheduled run's own behavior # (see genre_review_service.run's docstring), so a preview accurately # shows what "Fix genres now" would actually do. result = await genre_review_service.run(apply=False, force=True, triggered_by="manual") if result is None: return RedirectResponse(url="/genres?skipped=1", status_code=303) return RedirectResponse(url="/genres", status_code=303) @router.post("/apply") async def apply(user: dict = Depends(require_auth)): result = await genre_review_service.run(apply=True, force=True, triggered_by="manual") if result is None: return RedirectResponse(url="/genres?skipped=1", status_code=303) return RedirectResponse(url="/genres", status_code=303) @router.post("/lock") async def lock( artist: str = Form(...), genre: str = Form(...), user: dict = Depends(require_auth), ): changed_by = user.get("email") or user.get("sub", "unknown") await genre_review_service.lock_artist_genre(artist, genre, changed_by) return RedirectResponse(url="/genres", status_code=303)