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}