feat: background tasks manager and book sentence mining for CEFR tests

This commit is contained in:
Antigravity committed 2026-10-06 16:36:39 +03:00
1 parent 332286b641
commit c9da5bd351
11 files changed
+1377 -16

No files matched your search

+116
View File
@@ -12,6 +12,8 @@ from fastapi.middleware.gzip import GZipMiddleware
from pydantic import BaseModel, Field
import httpx
import asyncio
from app import config
from app.database import init_db, get_db
from app.auth import (
@@ -45,6 +47,14 @@ from app.grammar import (
record_grammar_completion,
update_handwritten_status,
)
from app.tasks import (
create_task,
get_task_status,
get_active_task,
list_user_tasks,
cancel_task,
)
from app.miner import run_mining_task_background
@@ -210,6 +220,12 @@ class GrammarHandwrittenRequest(BaseModel):
handwritten: bool
class MineBooksRequest(BaseModel):
book_id: Optional[int] = None
level: str = "A1"
count: int = Field(15, ge=1, le=50)
@@ -1557,3 +1573,103 @@ async def api_grammar_handwritten(
return update_handwritten_status(conn, user["id"], req.unit_id, req.handwritten)
# ---------------------------------------------------------------------------
# Background Tasks & Book Sentence Mining Endpoints
# ---------------------------------------------------------------------------
@app.post("/api/tasks/mine-books")
async def api_tasks_mine_books(
req: MineBooksRequest,
user: dict = Depends(require_user),
):
"""
Starts an asynchronous background task to mine CEFR Cloze Tap questions
from user's uploaded books (EPUB, TXT, PDF).
"""
level = req.level.upper().strip()
if level not in ["A1", "A2", "B1", "B2", "ALL"]:
raise HTTPException(status_code=400, detail="Invalid CEFR level")
count = max(1, min(req.count, 50))
with get_db() as conn:
active = get_active_task(conn, user["id"], task_type="mine_books")
if active:
return {
"status": "already_running",
"message": "Сборка уже выполняется в фоне",
"task": active,
}
task_id = create_task(
conn,
user_id=user["id"],
task_type="mine_books",
params={
"book_id": req.book_id,
"level": level,
"count": count,
},
total=count,
)
# Launch background asyncio task
asyncio.create_task(
run_mining_task_background(
task_id=task_id,
user_id=user["id"],
book_id=req.book_id,
target_level=level,
target_count=count,
)
)
return {
"status": "ok",
"task_id": task_id,
"message": f"Запущена фоновая сборка тестов ({level})",
}
@app.get("/api/tasks/active")
async def api_tasks_active(
task_type: Optional[str] = Query(None),
user: dict = Depends(require_user),
):
"""
Returns current user's active background task (if any).
"""
with get_db() as conn:
task = get_active_task(conn, user["id"], task_type=task_type)
return {"task": task}
@app.get("/api/tasks/{task_id}")
async def api_tasks_status(
task_id: int,
user: dict = Depends(require_user),
):
"""
Returns status and progress of a background task.
"""
with get_db() as conn:
task = get_task_status(conn, task_id, user_id=user["id"])
if not task:
raise HTTPException(status_code=404, detail="Task not found")
return {"task": task}
@app.post("/api/tasks/{task_id}/cancel")
async def api_tasks_cancel(
task_id: int,
user: dict = Depends(require_user),
):
"""
Cancels a running background task.
"""
with get_db() as conn:
cancelled = cancel_task(conn, task_id, user_id=user["id"])
return {"status": "ok" if cancelled else "not_found", "cancelled": cancelled}