feat(07-04): POST /classify re-queues Celery, removes synchronous classify (D-11)

- Replace synchronous await classifier.classify_document() with extract_and_classify.delay()
- Set doc.status='processing' and commit before dispatching Celery task
- Return {'document_id': str, 'status': 'processing'} instead of assigned topics
- Promote test_reclassify_requeues_celery from xfail stub to passing test
- Add test_reclassify_cross_user_returns_404 for IDOR security invariant (T-07-10)
This commit is contained in:
curo1305
2026-06-04 23:10:49 +02:00
parent e9ee5d4ba5
commit 63cd707d52
2 changed files with 111 additions and 12 deletions
+10 -8
View File
@@ -714,14 +714,17 @@ async def delete_document(
async def classify_document(
request: Request,
doc_id: str,
body: dict = {},
session: AsyncSession = Depends(get_db),
current_user: User = Depends(get_regular_user),
):
"""Reclassify a document's topics on demand.
"""Re-queue a document for classification via Celery (D-11).
Sets doc.status='processing', commits, dispatches extract_and_classify.delay(),
and returns {'document_id': str, 'status': 'processing'}.
D-16: requires authenticated regular user. Asserts ownership — cross-user
classify returns 404 (not 403) to avoid information leakage (T-03-11).
T-07-10: ownership enforced here; IDOR returns 404 per STATE.md policy.
"""
request.state.current_user = current_user
try:
@@ -733,13 +736,12 @@ async def classify_document(
if doc is None or doc.user_id != current_user.id:
raise HTTPException(404, "Document not found")
topic_names = body.get("topics") if body else None
try:
topics = await classifier.classify_document(session, doc_id, topic_names)
except Exception as e:
raise HTTPException(500, f"Classification failed: {e}")
doc.status = "processing"
await session.commit()
return {"topics": topics}
extract_and_classify.delay(str(doc.id))
return {"document_id": str(doc.id), "status": "processing"}
# ── Range header parsing helper ───────────────────────────────────────────────