149 lines
6.1 KiB
Python
149 lines
6.1 KiB
Python
import hashlib
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, Form, HTTPException, UploadFile
|
|
|
|
from app.config import settings
|
|
from app.services.d2l import D2LService
|
|
from app.services.paperless import PaperlessService
|
|
from app.services.repository import get_repository
|
|
from app.services.store import JsonStore
|
|
from app.services.webhooks import WebhookService
|
|
|
|
|
|
router = APIRouter(prefix="/api/assignments", tags=["assignments"])
|
|
store = JsonStore()
|
|
repo = get_repository()
|
|
d2l = D2LService()
|
|
paperless = PaperlessService()
|
|
webhooks = WebhookService()
|
|
|
|
|
|
@router.get("")
|
|
async def list_assignments():
|
|
courses = {item["id"]: item for item in d2l.get_courses()}
|
|
items = repo.list_assignments()
|
|
submissions = repo.list_submissions()
|
|
submission_counts = {}
|
|
for submission in submissions:
|
|
submission_counts[submission.get("assignment_id")] = submission_counts.get(submission.get("assignment_id"), 0) + 1
|
|
for item in items:
|
|
item["submission_count"] = submission_counts.get(item.get("id"), 0)
|
|
item["course"] = courses.get(item.get("course_id"), {})
|
|
return {"items": items, "backend": repo.backend}
|
|
|
|
|
|
|
|
|
|
@router.get('/submissions')
|
|
async def list_submissions():
|
|
return {"items": repo.list_submissions(), "backend": repo.backend}
|
|
|
|
@router.post("/intake")
|
|
async def intake_assignment(
|
|
file: UploadFile,
|
|
class_id: str = Form(...),
|
|
class_name: str = Form(...),
|
|
assignment_name: str = Form(...),
|
|
version: str = Form("v1"),
|
|
submission_kind: str = Form("assignment_turn_in"),
|
|
semester: str = Form(""),
|
|
course_code: str = Form(""),
|
|
paper_kind: str = Form(""),
|
|
source: str = Form("doris-schoolhouse"),
|
|
telegram_chat_id: str = Form(""),
|
|
telegram_message_id: str = Form(""),
|
|
notes: str = Form(""),
|
|
):
|
|
assignments = d2l.get_assignments(class_id)
|
|
matching = next((item for item in assignments if item.get("title") == assignment_name), None)
|
|
if not matching:
|
|
raise HTTPException(status_code=404, detail="Assignment not found in cached D2L data for selected class.")
|
|
|
|
upload_root = Path(settings.upload_tmp_dir)
|
|
upload_root.mkdir(parents=True, exist_ok=True)
|
|
safe_name = Path(file.filename or "upload.bin").name
|
|
submission_id = store.new_id("submission")
|
|
stored_path = upload_root / f"{submission_id}-{safe_name}"
|
|
payload = await file.read()
|
|
stored_path.write_bytes(payload)
|
|
checksum = hashlib.sha256(payload).hexdigest()
|
|
|
|
record = {
|
|
"id": submission_id,
|
|
"assignment_id": matching["id"],
|
|
"class_id": class_id,
|
|
"class_name": class_name,
|
|
"assignment_name": assignment_name,
|
|
"version_label": version,
|
|
"submission_kind": submission_kind,
|
|
"semester": semester or None,
|
|
"course_code": course_code or (course.get("code") if course else None),
|
|
"paper_kind": paper_kind or None,
|
|
"source": source or "doris-schoolhouse",
|
|
"telegram_chat_id": telegram_chat_id or None,
|
|
"telegram_message_id": telegram_message_id or None,
|
|
"notes": notes,
|
|
"original_filename": safe_name,
|
|
"stored_path": str(stored_path),
|
|
"mime_type": file.content_type or "application/octet-stream",
|
|
"file_size_bytes": len(payload),
|
|
"checksum_sha256": checksum,
|
|
"status": "uploaded-local",
|
|
"received_at": store.now(),
|
|
}
|
|
record["paperless_handoff"] = paperless.assignment_intake_payload(record)
|
|
created = repo.create_submission(record)
|
|
return {"status": "ok", "submission": created, "backend": repo.backend}
|
|
|
|
|
|
@router.post("/{submission_id}/confirm-paperless")
|
|
async def confirm_assignment_paperless(submission_id: str):
|
|
submissions = repo.list_submissions()
|
|
current = next((item for item in submissions if str(item.get("id")) == str(submission_id)), None)
|
|
if not current:
|
|
raise HTTPException(status_code=404, detail="Submission not found.")
|
|
handoff = paperless.assignment_intake_payload(current)
|
|
file_path = current.get("stored_path") or current.get("local_file")
|
|
if not file_path or not Path(file_path).exists():
|
|
raise HTTPException(status_code=400, detail="Stored upload file is missing.")
|
|
fields = {
|
|
"class_name": handoff["class_name"],
|
|
"assignment_name": handoff["assignment_name"],
|
|
"submission_kind": handoff["submission_kind"],
|
|
"notes": handoff.get("notes", ""),
|
|
"source": handoff.get("source", "doris-schoolhouse"),
|
|
}
|
|
for optional_key in ("semester", "course_code", "paper_kind", "telegram_chat_id", "telegram_message_id"):
|
|
value = handoff.get(optional_key)
|
|
if value:
|
|
fields[optional_key] = value
|
|
response = webhooks.post_multipart(handoff["target_url"], fields, "document", file_path)
|
|
body = response.get("body", {}) if isinstance(response, dict) else {}
|
|
body_obj = body[0] if isinstance(body, list) and body and isinstance(body[0], dict) else (body if isinstance(body, dict) else {})
|
|
ok = bool(response.get("ok", False)) if isinstance(response, dict) else False
|
|
updated = repo.update_submission(submission_id, {
|
|
"status": "paperless-queued" if ok else "paperless-error",
|
|
"confirmed_at": store.now(),
|
|
"webhook_response": response,
|
|
"paperless_document_id": body_obj.get("paperless_document_id") or body_obj.get("document_id"),
|
|
"paperless_title": body_obj.get("paperless_title") or body_obj.get("title") or handoff.get("assignment_name"),
|
|
})
|
|
return {"status": "ok" if ok else "error", "submission": updated or current, "handoff": handoff, "webhook": response}
|
|
|
|
|
|
@router.post("/{assignment_id}/status")
|
|
async def update_assignment_status(assignment_id: str, status: str = Form(...)):
|
|
assignments = repo.list_assignments()
|
|
updated = None
|
|
for item in assignments:
|
|
if str(item.get("id")) == str(assignment_id):
|
|
item["status"] = status
|
|
item["updated_at"] = store.now()
|
|
updated = item
|
|
break
|
|
if not updated:
|
|
raise HTTPException(status_code=404, detail="Assignment not found.")
|
|
repo.replace_assignments(assignments)
|
|
return {"status": "ok", "assignment": updated}
|