from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from sqlalchemy.orm import selectinload from app.database import get_db from app.models.user import User from app.models.problem import Problem from app.models.submission import Submission from app.schemas.submission import SubmissionCreate, SubmissionResponse, SubmissionListResponse from app.dependencies import get_current_user from app.services.scoring import evaluate_submission router = APIRouter() async def process_submission(submission_id: int, db_url: str): """Background task to evaluate submission""" from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker from sqlalchemy import select from sqlalchemy.orm import selectinload engine = create_async_engine(db_url) async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as db: # Get submission with problem and test cases result = await db.execute( select(Submission) .options(selectinload(Submission.problem).selectinload(Problem.test_cases)) .where(Submission.id == submission_id) ) submission = result.scalar_one_or_none() if not submission: return # Update status to judging submission.status = "judging" await db.commit() try: # Evaluate submission result = await evaluate_submission( source_code=submission.source_code, language_id=submission.language_id, test_cases=submission.problem.test_cases, total_points=submission.problem.total_points, time_limit_ms=submission.problem.time_limit_ms, memory_limit_kb=submission.problem.memory_limit_kb, ) # Update submission with results submission.status = result["status"] submission.score = result["score"] submission.total_points = result["total_points"] submission.tests_passed = result["tests_passed"] submission.tests_total = result["tests_total"] submission.execution_time_ms = result["execution_time_ms"] submission.memory_used_kb = result["memory_used_kb"] submission.judge_response = result["details"] await db.commit() except Exception as e: submission.status = "internal_error" submission.judge_response = {"error": str(e)} await db.commit() await engine.dispose() @router.post("/", response_model=SubmissionResponse, status_code=status.HTTP_201_CREATED) async def create_submission( submission_data: SubmissionCreate, background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): # Check if problem exists result = await db.execute( select(Problem) .options(selectinload(Problem.test_cases)) .where(Problem.id == submission_data.problem_id) ) problem = result.scalar_one_or_none() if not problem: raise HTTPException(status_code=404, detail="Problem not found") # Create submission submission = Submission( user_id=current_user.id, problem_id=submission_data.problem_id, contest_id=submission_data.contest_id, source_code=submission_data.source_code, language_id=submission_data.language_id, language_name=submission_data.language_name, status="pending", total_points=problem.total_points, tests_total=len(problem.test_cases), ) db.add(submission) await db.commit() await db.refresh(submission) # Add background task to process submission from app.config import settings background_tasks.add_task(process_submission, submission.id, settings.database_url) return submission @router.get("/{submission_id}", response_model=SubmissionResponse) async def get_submission( submission_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): result = await db.execute( select(Submission).where(Submission.id == submission_id) ) submission = result.scalar_one_or_none() if not submission: raise HTTPException(status_code=404, detail="Submission not found") # Users can only see their own submissions if submission.user_id != current_user.id and current_user.role != "admin": raise HTTPException(status_code=403, detail="Access denied") return submission @router.get("/", response_model=list[SubmissionListResponse]) async def get_my_submissions( problem_id: int | None = None, contest_id: int | None = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): query = ( select(Submission) .options(selectinload(Submission.problem)) .where(Submission.user_id == current_user.id) ) if problem_id: query = query.where(Submission.problem_id == problem_id) if contest_id: query = query.where(Submission.contest_id == contest_id) query = query.order_by(Submission.created_at.desc()) result = await db.execute(query) submissions = result.scalars().all() # Add problem_title to each submission return [ SubmissionListResponse( id=s.id, problem_id=s.problem_id, problem_title=s.problem.title if s.problem else None, contest_id=s.contest_id, language_name=s.language_name, status=s.status, score=s.score, total_points=s.total_points, tests_passed=s.tests_passed, tests_total=s.tests_total, created_at=s.created_at, ) for s in submissions ] @router.get("/problem/{problem_id}", response_model=list[SubmissionListResponse]) async def get_submissions_by_problem( problem_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): # Users can only see their own submissions result = await db.execute( select(Submission) .where( Submission.problem_id == problem_id, Submission.user_id == current_user.id, ) .order_by(Submission.created_at.desc()) ) return result.scalars().all()