diff --git a/apps/api/src/routers/courses/assignments.py b/apps/api/src/routers/courses/assignments.py index 277ee134..953f13a0 100644 --- a/apps/api/src/routers/courses/assignments.py +++ b/apps/api/src/routers/courses/assignments.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, Request, UploadFile +from fastapi import APIRouter, Depends, Request, UploadFile, HTTPException from src.db.courses.assignments import ( AssignmentCreate, AssignmentRead, @@ -295,9 +295,16 @@ async def api_read_user_assignment_task_submissions_me( """ Read task submissions for an assignment from a user """ - return await read_user_assignment_task_submissions_me( + result = await read_user_assignment_task_submissions_me( request, assignment_task_uuid, current_user, db_session ) + if result is None: + # Return 404 if no submission exists (maintains current frontend behavior) + raise HTTPException( + status_code=404, + detail="Assignment Task Submission not found", + ) + return result @router.get("/{assignment_uuid}/tasks/{assignment_task_uuid}/submissions") diff --git a/apps/api/src/services/courses/activities/assignments.py b/apps/api/src/services/courses/activities/assignments.py index b18a6d18..c1182d00 100644 --- a/apps/api/src/services/courses/activities/assignments.py +++ b/apps/api/src/services/courses/activities/assignments.py @@ -764,9 +764,15 @@ async def handle_assignment_task_submission( # SECURITY: Instructors/admins need update permission to grade await courses_rbac_check_for_assignments(request, course.course_uuid, current_user, "update", db_session) - # Try to find existing submission if UUID is provided - assignment_task_submission = None - if assignment_task_submission_uuid: + # Try to find existing submission by user_id and assignment_task_id first (for save progress functionality) + statement = select(AssignmentTaskSubmission).where( + AssignmentTaskSubmission.assignment_task_id == assignment_task.id, + AssignmentTaskSubmission.user_id == current_user.id, + ) + assignment_task_submission = db_session.exec(statement).first() + + # If no submission found by user+task, try to find by UUID if provided (for specific submission updates) + if not assignment_task_submission and assignment_task_submission_uuid: statement = select(AssignmentTaskSubmission).where( AssignmentTaskSubmission.assignment_task_submission_uuid == assignment_task_submission_uuid ) @@ -889,13 +895,54 @@ async def read_user_assignment_task_submissions_me( current_user: PublicUser | AnonymousUser, db_session: Session, ): - return await read_user_assignment_task_submissions( - request, - assignment_task_uuid, - current_user.id, - current_user, - db_session, + # Check if assignment task exists + statement = select(AssignmentTask).where( + AssignmentTask.assignment_task_uuid == assignment_task_uuid ) + assignment_task = db_session.exec(statement).first() + + if not assignment_task: + raise HTTPException( + status_code=404, + detail="Assignment Task not found", + ) + + # Check if assignment task submission exists + statement = select(AssignmentTaskSubmission).where( + AssignmentTaskSubmission.assignment_task_id == assignment_task.id, + AssignmentTaskSubmission.user_id == current_user.id, + ) + assignment_task_submission = db_session.exec(statement).first() + + if not assignment_task_submission: + # Return None instead of raising an error for cases where no submission exists yet + return None + + # Check if assignment exists + statement = select(Assignment).where(Assignment.id == assignment_task.assignment_id) + assignment = db_session.exec(statement).first() + + if not assignment: + raise HTTPException( + status_code=404, + detail="Assignment not found", + ) + + # Check if course exists + statement = select(Course).where(Course.id == assignment.course_id) + course = db_session.exec(statement).first() + + if not course: + raise HTTPException( + status_code=404, + detail="Course not found", + ) + + # RBAC check + await courses_rbac_check_for_assignments(request, course.course_uuid, current_user, "read", db_session) + + # return assignment task submission read + return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission) async def read_assignment_task_submissions( diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx index c3434c92..a5cc6858 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFileObject.tsx @@ -106,9 +106,9 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta return; } - // Save the quiz to the server + // Save the file submission to the server const values = { - assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid, + assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid || null, task_submission: userSubmissions, grade: 0, task_submission_grade_feedback: '', @@ -121,6 +121,13 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta }); toast.success('Task saved successfully'); setShowSavingDisclaimer(false); + // Update userSubmissions with the returned UUID for future updates + const updatedUserSubmissions = { + ...userSubmissions, + assignment_task_submission_uuid: res.data?.assignment_task_submission_uuid || userSubmissions.assignment_task_submission_uuid + }; + setUserSubmissions(updatedUserSubmissions); + setInitialUserSubmissions(updatedUserSubmissions); } else { toast.error('Error saving task, please retry later.'); } diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFormObject.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFormObject.tsx index 0211e903..ab47ee2e 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFormObject.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskFormObject.tsx @@ -187,6 +187,7 @@ function TaskFormObject({ view, assignmentTaskUUID, user_id }: TaskFormObjectPro } const values = { + assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid || null, task_submission: userSubmissions, grade: 0, task_submission_grade_feedback: '', @@ -201,7 +202,13 @@ function TaskFormObject({ view, assignmentTaskUUID, user_id }: TaskFormObjectPro if (res) { toast.success('Form submitted successfully!'); - setInitialUserSubmissions(userSubmissions); + // Update userSubmissions with the returned UUID for future updates + const updatedUserSubmissions = { + ...userSubmissions, + assignment_task_submission_uuid: res.data?.assignment_task_submission_uuid || userSubmissions.assignment_task_submission_uuid + }; + setUserSubmissions(updatedUserSubmissions); + setInitialUserSubmissions(updatedUserSubmissions); setShowSavingDisclaimer(false); } else { console.error('Submission error:', res); diff --git a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx index 7a8d3294..5c8472b5 100644 --- a/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/assignments/[assignmentuuid]/_components/TaskEditor/Subs/TaskTypes/TaskQuizObject.tsx @@ -221,6 +221,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro // Save the quiz to the server const values = { + assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid || null, task_submission: updatedUserSubmissions, grade: 0, task_submission_grade_feedback: '', @@ -234,7 +235,13 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro }); toast.success('Task saved successfully'); setShowSavingDisclaimer(false); - setUserSubmissions(updatedUserSubmissions); + // Update userSubmissions with the returned UUID for future updates + const updatedUserSubmissionsWithUUID = { + ...updatedUserSubmissions, + assignment_task_submission_uuid: res.data?.assignment_task_submission_uuid || userSubmissions.assignment_task_submission_uuid + }; + setUserSubmissions(updatedUserSubmissionsWithUUID); + setInitialUserSubmissions(updatedUserSubmissionsWithUUID); } else { toast.error('Error saving task, please retry later.'); }