163 lines
5.2 KiB
Python
163 lines
5.2 KiB
Python
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .. import config
|
|
from ..db import BookRequest, Library, get_session
|
|
from ..importer import tagger
|
|
|
|
router = APIRouter(prefix="/api/requests", tags=["requests"])
|
|
|
|
|
|
class RequestIn(BaseModel):
|
|
library_id: int
|
|
title: str
|
|
authors: str = ""
|
|
narrator: str = ""
|
|
external_id: str = ""
|
|
year: int | None = None
|
|
series: str = ""
|
|
volume: int | None = None
|
|
cover_url: str = ""
|
|
|
|
|
|
class RequestOut(RequestIn):
|
|
id: int
|
|
media_type: str
|
|
status: str
|
|
imported_path: str
|
|
library_name: str = ""
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
def _out(r: BookRequest) -> RequestOut:
|
|
out = RequestOut.model_validate(r)
|
|
out.library_name = r.library.name if r.library else ""
|
|
return out
|
|
|
|
|
|
@router.get("", response_model=list[RequestOut])
|
|
def list_requests(status: str | None = None, media_type: str | None = None,
|
|
library_id: int | None = None, session: Session = Depends(get_session)):
|
|
stmt = select(BookRequest).order_by(BookRequest.created_at.desc())
|
|
if status:
|
|
stmt = stmt.where(BookRequest.status == status)
|
|
if media_type:
|
|
stmt = stmt.where(BookRequest.media_type == media_type)
|
|
if library_id:
|
|
stmt = stmt.where(BookRequest.library_id == library_id)
|
|
return [_out(r) for r in session.scalars(stmt).all()]
|
|
|
|
|
|
@router.post("", response_model=RequestOut)
|
|
def create_request(data: RequestIn, session: Session = Depends(get_session)):
|
|
lib = session.get(Library, data.library_id)
|
|
if not lib:
|
|
raise HTTPException(404, "library not found")
|
|
req = BookRequest(media_type=lib.media_type, **data.model_dump())
|
|
session.add(req)
|
|
session.commit()
|
|
session.refresh(req)
|
|
return _out(req)
|
|
|
|
|
|
class BulkRequestIn(BaseModel):
|
|
library_id: int
|
|
series: str
|
|
authors: str = ""
|
|
narrator: str = ""
|
|
volume_from: int
|
|
volume_to: int
|
|
year: int | None = None
|
|
cover_url: str = ""
|
|
|
|
|
|
@router.post("/bulk", response_model=list[RequestOut])
|
|
def create_bulk(data: BulkRequestIn, session: Session = Depends(get_session)):
|
|
lib = session.get(Library, data.library_id)
|
|
if not lib:
|
|
raise HTTPException(404, "library not found")
|
|
if data.volume_from > data.volume_to:
|
|
raise HTTPException(400, "volume_from must be <= volume_to")
|
|
if data.volume_to - data.volume_from > 999:
|
|
raise HTTPException(400, "at most 1000 requests per bulk call")
|
|
width = max(2, len(str(data.volume_to)))
|
|
reqs = []
|
|
for n in range(data.volume_from, data.volume_to + 1):
|
|
req = BookRequest(
|
|
media_type=lib.media_type,
|
|
library_id=lib.id,
|
|
title=f"{data.series} Folge {n:0{width}d}",
|
|
authors=data.authors,
|
|
narrator=data.narrator,
|
|
series=data.series,
|
|
volume=n,
|
|
year=data.year,
|
|
cover_url=data.cover_url,
|
|
)
|
|
session.add(req)
|
|
reqs.append(req)
|
|
session.commit()
|
|
for r in reqs:
|
|
session.refresh(r)
|
|
return [_out(r) for r in reqs]
|
|
|
|
|
|
@router.put("/{request_id}", response_model=RequestOut)
|
|
def update_request(request_id: int, data: RequestIn, session: Session = Depends(get_session)):
|
|
req = session.get(BookRequest, request_id)
|
|
if not req:
|
|
raise HTTPException(404, "request not found")
|
|
lib = session.get(Library, data.library_id)
|
|
if not lib:
|
|
raise HTTPException(404, "library not found")
|
|
for field, value in data.model_dump().items():
|
|
setattr(req, field, value)
|
|
req.media_type = lib.media_type
|
|
session.commit()
|
|
session.refresh(req)
|
|
return _out(req)
|
|
|
|
|
|
@router.post("/{request_id}/retag")
|
|
def retag_request(request_id: int, session: Session = Depends(get_session)):
|
|
"""Rewrite the audio tags of an already imported request from its current
|
|
metadata (e.g. after adding series/volume/narrator later)."""
|
|
req = session.get(BookRequest, request_id)
|
|
if not req:
|
|
raise HTTPException(404, "request not found")
|
|
if req.media_type != "audiobook":
|
|
raise HTTPException(400, "retag is only supported for audiobooks")
|
|
if req.status != "imported" or not req.imported_path:
|
|
raise HTTPException(400, "request is not imported yet")
|
|
path = Path(req.imported_path)
|
|
if path.is_dir():
|
|
files = sorted(
|
|
f for f in path.iterdir()
|
|
if f.is_file() and f.suffix.lower() in config.AUDIOBOOK_EXTENSIONS
|
|
)
|
|
if not files:
|
|
raise HTTPException(404, f"no audio files in {path}")
|
|
for i, f in enumerate(files, 1):
|
|
tagger.tag_audio(f, req, track=i, total=len(files))
|
|
return {"ok": True, "files": len(files)}
|
|
if path.is_file():
|
|
tagger.tag_audio(path, req)
|
|
return {"ok": True, "files": 1}
|
|
raise HTTPException(404, f"imported path no longer exists: {path}")
|
|
|
|
|
|
@router.delete("/{request_id}")
|
|
def delete_request(request_id: int, session: Session = Depends(get_session)):
|
|
req = session.get(BookRequest, request_id)
|
|
if not req:
|
|
raise HTTPException(404, "request not found")
|
|
session.delete(req)
|
|
session.commit()
|
|
return {"ok": True}
|