Files
alembic/app/routers/genres.py
T
andrew 8f1fc458f6 Background long-running actions; confirm destructive ones
- Run now (jobs and playlists), dedup scan, genre preview/apply, and manual
  import now dispatch via BackgroundTasks and redirect immediately, instead
  of awaiting a job that can run for the better part of an hour and hang the
  browser or reverse proxy. Progress shows in the Jobs runs table (which
  already polls); if the pipeline is busy the run records skipped_lock there.
- Fix the import banner, which claimed work was ongoing after the request
  had actually blocked to completion; it now reflects the backgrounded start.
- Add confirmation prompts to the dedup per-row and bulk delete and to
  "Fix genres now", matching the existing confirms on library and playlist
  deletes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:31:54 -06:00

55 lines
2.2 KiB
Python

from fastapi import APIRouter, BackgroundTasks, 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(background: BackgroundTasks, 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. Runs in the background;
# the last-run table below fills in when it finishes.
background.add_task(genre_review_service.run, apply=False, force=True, triggered_by="manual")
return RedirectResponse(url="/genres?started=1", status_code=303)
@router.post("/apply")
async def apply(background: BackgroundTasks, user: dict = Depends(require_auth)):
background.add_task(genre_review_service.run, apply=True, force=True, triggered_by="manual")
return RedirectResponse(url="/genres?started=1", 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)