feat(requests): add bulk update, bulk delete and a count endpoint
This commit is contained in:
1 parent
f7f89a3cc7
commit
07c988c443
2 files changed
+70
-1
No files matched your search
@@ -1270,3 +1270,27 @@ def test_connections_hear_about_committed_rows(client, monkeypatch):
|
|||||||
calls.clear()
|
calls.clear()
|
||||||
client.delete(f"/api/requests/{req_id}")
|
client.delete(f"/api/requests/{req_id}")
|
||||||
assert calls == [("POST", "http://hook/in")] # nothing new on disk, no scan
|
assert calls == [("POST", "http://hook/in")] # nothing new on disk, no scan
|
||||||
|
|
||||||
|
|
||||||
|
def test_bulk_move_delete_and_count(client):
|
||||||
|
def lib(name, media_type):
|
||||||
|
return client.post("/api/libraries", json={
|
||||||
|
"name": name, "media_type": media_type,
|
||||||
|
"root_path": str(client.tmp_path / name)}).json()["id"]
|
||||||
|
|
||||||
|
a, b, audio = lib("a", "ebook"), lib("b", "ebook"), lib("audio", "audiobook")
|
||||||
|
ids = [client.post("/api/requests", json={"library_id": a, "title": f"T{i}"}).json()["id"]
|
||||||
|
for i in range(3)]
|
||||||
|
assert client.get("/api/requests/count?status=missing").json() == {"count": 3}
|
||||||
|
|
||||||
|
assert client.put("/api/requests/bulk", json={
|
||||||
|
"ids": ids[:2], "library_id": audio}).status_code == 400
|
||||||
|
assert client.put("/api/requests/bulk", json={
|
||||||
|
"ids": ids[:2], "library_id": b}).json() == {"updated": 2}
|
||||||
|
assert [r["id"] for r in client.get(f"/api/requests?library_id={b}").json()] \
|
||||||
|
== sorted(ids[:2], reverse=True)
|
||||||
|
|
||||||
|
assert client.request("DELETE", "/api/requests/bulk",
|
||||||
|
json={"ids": ids[1:]}).json() == {"deleted": 2}
|
||||||
|
assert client.get("/api/requests/count?status=missing").json() == {"count": 1}
|
||||||
|
assert [r["event"] for r in client.get("/api/history").json()] == ["deleted"] * 2
|
||||||
+46
-1
@@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from sqlalchemy import select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from .. import config, connect
|
from .. import config, connect
|
||||||
@@ -176,6 +176,51 @@ def create_bulk_items(data: BulkItemsIn, session: Session = Depends(get_session)
|
|||||||
return {"created": [_out(r) for r in created], "skipped": skipped}
|
return {"created": [_out(r) for r in created], "skipped": skipped}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/count")
|
||||||
|
def count_requests(status: str | None = None, session: Session = Depends(get_session)):
|
||||||
|
stmt = select(func.count()).select_from(BookRequest)
|
||||||
|
if status:
|
||||||
|
stmt = stmt.where(BookRequest.status == status)
|
||||||
|
return {"count": session.scalar(stmt)}
|
||||||
|
|
||||||
|
|
||||||
|
class BulkIdsIn(BaseModel):
|
||||||
|
ids: list[int]
|
||||||
|
|
||||||
|
|
||||||
|
class BulkUpdateIn(BulkIdsIn):
|
||||||
|
library_id: int
|
||||||
|
|
||||||
|
|
||||||
|
# registered before the /{request_id} routes, which would try "bulk" as an id
|
||||||
|
@router.put("/bulk")
|
||||||
|
def update_bulk(data: BulkUpdateIn, session: Session = Depends(get_session)):
|
||||||
|
"""Move requests to another library. Imported files stay where they are
|
||||||
|
until the request is relocated."""
|
||||||
|
lib = session.get(Library, data.library_id)
|
||||||
|
if not lib:
|
||||||
|
raise HTTPException(404, "library not found")
|
||||||
|
reqs = session.scalars(select(BookRequest).where(BookRequest.id.in_(data.ids))).all()
|
||||||
|
if any(r.media_type != lib.media_type for r in reqs):
|
||||||
|
raise HTTPException(400, "library has a different media type")
|
||||||
|
for r in reqs:
|
||||||
|
r.library_id = lib.id
|
||||||
|
session.commit()
|
||||||
|
return {"updated": len(reqs)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/bulk")
|
||||||
|
def delete_bulk(data: BulkIdsIn, background: BackgroundTasks,
|
||||||
|
session: Session = Depends(get_session)):
|
||||||
|
reqs = session.scalars(select(BookRequest).where(BookRequest.id.in_(data.ids))).all()
|
||||||
|
rows = [record(session, "deleted", r, source=r.imported_path) for r in reqs]
|
||||||
|
for r in reqs:
|
||||||
|
session.delete(r)
|
||||||
|
session.commit()
|
||||||
|
connect.schedule(background, rows)
|
||||||
|
return {"deleted": len(reqs)}
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{request_id}", response_model=RequestOut)
|
@router.put("/{request_id}", response_model=RequestOut)
|
||||||
def update_request(request_id: int, data: RequestIn, session: Session = Depends(get_session)):
|
def update_request(request_id: int, data: RequestIn, session: Session = Depends(get_session)):
|
||||||
req = session.get(BookRequest, request_id)
|
req = session.get(BookRequest, request_id)
|
||||||
|
|||||||
Reference in new issue
Block a user