1import { Hono } from "https://esm.sh/hono@3.11.7";
2import type { SubmitExamRequest, ApiResponse, User, ExamSubmission, Question, GradeResult } from "../../shared/types.ts";
3import * as db from "../database/queries.ts";
4import { requireAuth, requireInstructor } from "./auth.ts";
65
66 if (isNaN(examId)) {
67 return c.json<ApiResponse>({ success: false, error: "Invalid exam ID" }, 400);
68 }
69
70 if (user.role !== 'student') {
71 return c.json<ApiResponse>({ success: false, error: "Only students can submit exams" }, 403);
72 }
73
75 const exam = await db.getExamById(examId);
76 if (!exam) {
77 return c.json<ApiResponse>({ success: false, error: "Exam not found" }, 404);
78 }
79
80 if (!exam.is_published) {
81 return c.json<ApiResponse>({ success: false, error: "Exam is not published" }, 403);
82 }
83
85 const enrolled = await db.isUserEnrolled(user.id, exam.course_id);
86 if (!enrolled) {
87 return c.json<ApiResponse>({ success: false, error: "Not enrolled in this course" }, 403);
88 }
89
91 const existingSubmission = await db.getSubmission(examId, user.id);
92 if (existingSubmission) {
93 return c.json<ApiResponse>({ success: false, error: "Exam already submitted" }, 400);
94 }
95
99 const now = new Date();
100 if (now > dueDate) {
101 return c.json<ApiResponse>({ success: false, error: "Exam deadline has passed" }, 400);
102 }
103 }
137 }
138
139 return c.json<ApiResponse<{ submission: ExamSubmission; gradeResults: GradeResult[] }>>({
140 success: true,
141 data: { submission, gradeResults }
143 } catch (error) {
144 console.error("Submit exam error:", error);
145 return c.json<ApiResponse>({ success: false, error: "Failed to submit exam" }, 500);
146 }
147});
154
155 if (isNaN(examId)) {
156 return c.json<ApiResponse>({ success: false, error: "Invalid exam ID" }, 400);
157 }
158
160 const exam = await db.getExamById(examId);
161 if (!exam) {
162 return c.json<ApiResponse>({ success: false, error: "Exam not found" }, 404);
163 }
164
165 const course = await db.getCourseById(exam.course_id);
166 if (!course || course.instructor_id !== user.id) {
167 return c.json<ApiResponse>({ success: false, error: "Access denied" }, 403);
168 }
169
170 const submissionList = await db.getExamSubmissions(examId);
171 return c.json<ApiResponse>({ success: true, data: submissionList });
172 } catch (error) {
173 console.error("Get submissions error:", error);
174 return c.json<ApiResponse>({ success: false, error: "Failed to get submissions" }, 500);
175 }
176});
183
184 if (isNaN(submissionId)) {
185 return c.json<ApiResponse>({ success: false, error: "Invalid submission ID" }, 400);
186 }
187
197
198 if (result.rows.length === 0) {
199 return c.json<ApiResponse>({ success: false, error: "Submission not found" }, 404);
200 }
201
206 if (user.role === 'student') {
207 if (submissionData.user_id !== user.id) {
208 return c.json<ApiResponse>({ success: false, error: "Access denied" }, 403);
209 }
210 } else if (user.role === 'instructor') {
211 const course = await db.getCourseById(submissionData.course_id);
212 if (!course || course.instructor_id !== user.id) {
213 return c.json<ApiResponse>({ success: false, error: "Access denied" }, 403);
214 }
215 }
218 const questions = await db.getExamQuestions(submissionData.exam_id);
219
220 return c.json<ApiResponse>({
221 success: true,
222 data: {
227 } catch (error) {
228 console.error("Get submission error:", error);
229 return c.json<ApiResponse>({ success: false, error: "Failed to get submission" }, 500);
230 }
231});
239
240 if (isNaN(submissionId)) {
241 return c.json<ApiResponse>({ success: false, error: "Invalid submission ID" }, 400);
242 }
243
244 if (typeof score !== 'number' || score < 0) {
245 return c.json<ApiResponse>({ success: false, error: "Invalid score" }, 400);
246 }
247
256
257 if (result.rows.length === 0) {
258 return c.json<ApiResponse>({ success: false, error: "Submission not found" }, 404);
259 }
260
262 const course = await db.getCourseById(submissionData.course_id);
263 if (!course || course.instructor_id !== user.id) {
264 return c.json<ApiResponse>({ success: false, error: "Access denied" }, 403);
265 }
266
267 // Validate score doesn't exceed max
268 if (score > submissionData.max_score) {
269 return c.json<ApiResponse>({
270 success: false,
271 error: `Score cannot exceed maximum of ${submissionData.max_score}`
274
275 await db.updateSubmissionScore(submissionId, score, user.id);
276 return c.json<ApiResponse>({ success: true, data: { message: "Score updated successfully" } });
277 } catch (error) {
278 console.error("Grade submission error:", error);
279 return c.json<ApiResponse>({ success: false, error: "Failed to update score" }, 500);
280 }
281});