refactor: P1-2 actions 层 DB 操作下沉到 data-access (exams/homework/questions/announcements)
This commit is contained in:
@@ -2,25 +2,24 @@
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { and, count, eq } from "drizzle-orm"
|
||||
|
||||
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { db } from "@/shared/db"
|
||||
import {
|
||||
classes,
|
||||
classEnrollments,
|
||||
classSubjectTeachers,
|
||||
exams,
|
||||
homeworkAnswers,
|
||||
homeworkAssignmentQuestions,
|
||||
homeworkAssignmentTargets,
|
||||
homeworkAssignments,
|
||||
homeworkSubmissions,
|
||||
} from "@/shared/db/schema"
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
|
||||
import { CreateHomeworkAssignmentSchema, GradeHomeworkSchema } from "./schema"
|
||||
import {
|
||||
createHomeworkAssignment,
|
||||
getActiveClassStudentIdsForHomework,
|
||||
getClassTeacherById,
|
||||
getExamWithQuestionsForHomework,
|
||||
getHomeworkSubmissionForPermission,
|
||||
getTeacherAssignedSubjectIds,
|
||||
gradeHomeworkAnswers,
|
||||
markHomeworkSubmitted,
|
||||
saveHomeworkAnswer,
|
||||
startHomeworkSubmission,
|
||||
} from "./data-access-write"
|
||||
|
||||
const parseStudentIds = (raw: string): string[] => {
|
||||
return raw
|
||||
@@ -69,64 +68,32 @@ export async function createHomeworkAssignmentAction(
|
||||
const input = parsed.data
|
||||
const publish = input.publish ?? true
|
||||
|
||||
const [classRow] = await db
|
||||
.select({ id: classes.id, teacherId: classes.teacherId })
|
||||
.from(classes)
|
||||
.where(eq(classes.id, input.classId))
|
||||
.limit(1)
|
||||
const classRow = await getClassTeacherById(input.classId)
|
||||
if (!classRow) return { success: false, message: "Class not found" }
|
||||
|
||||
const exam = await db.query.exams.findFirst({
|
||||
where: eq(exams.id, input.sourceExamId),
|
||||
with: {
|
||||
questions: {
|
||||
orderBy: (examQuestions, { asc }) => [asc(examQuestions.order)],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const exam = await getExamWithQuestionsForHomework(input.sourceExamId)
|
||||
if (!exam) return { success: false, message: "Exam not found" }
|
||||
|
||||
if (ctx.dataScope.type !== "all" && classRow.teacherId !== ctx.userId) {
|
||||
const assignedSubjectRows = await db
|
||||
.select({ subjectId: classSubjectTeachers.subjectId })
|
||||
.from(classSubjectTeachers)
|
||||
.where(and(eq(classSubjectTeachers.classId, input.classId), eq(classSubjectTeachers.teacherId, ctx.userId)))
|
||||
if (assignedSubjectRows.length === 0) {
|
||||
const assignedSubjectIds = await getTeacherAssignedSubjectIds(input.classId, ctx.userId)
|
||||
if (assignedSubjectIds.length === 0) {
|
||||
return { success: false, message: "Not assigned to this class" }
|
||||
}
|
||||
const assignedSubjectIds = new Set(assignedSubjectRows.map((r) => r.subjectId))
|
||||
const assignedSubjectSet = new Set(assignedSubjectIds)
|
||||
if (!exam.subjectId) {
|
||||
return { success: false, message: "Exam subject not set" }
|
||||
}
|
||||
if (!assignedSubjectIds.has(exam.subjectId)) {
|
||||
if (!assignedSubjectSet.has(exam.subjectId)) {
|
||||
return { success: false, message: "Not assigned to this subject" }
|
||||
}
|
||||
}
|
||||
|
||||
const assignmentId = createId()
|
||||
|
||||
const availableAt = input.availableAt ? new Date(input.availableAt) : null
|
||||
const dueAt = input.dueAt ? new Date(input.dueAt) : null
|
||||
const lateDueAt = input.lateDueAt ? new Date(input.lateDueAt) : null
|
||||
|
||||
const classScope =
|
||||
ctx.dataScope.type === "all"
|
||||
? eq(classes.id, input.classId)
|
||||
: classRow.teacherId === ctx.userId
|
||||
? eq(classes.teacherId, ctx.userId)
|
||||
: eq(classes.id, input.classId)
|
||||
|
||||
const classStudentIds = (
|
||||
await db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.innerJoin(classes, eq(classes.id, classEnrollments.classId))
|
||||
.where(
|
||||
and(eq(classEnrollments.classId, input.classId), eq(classEnrollments.status, "active"), classScope)
|
||||
)
|
||||
).map((r) => r.studentId)
|
||||
|
||||
const classStudentIds = await getActiveClassStudentIdsForHomework(
|
||||
input.classId,
|
||||
ctx.dataScope,
|
||||
ctx.userId,
|
||||
classRow.teacherId
|
||||
)
|
||||
const classStudentIdSet = new Set(classStudentIds)
|
||||
|
||||
const targetStudentIds =
|
||||
@@ -138,41 +105,27 @@ export async function createHomeworkAssignmentAction(
|
||||
return { success: false, message: "No active students in this class" }
|
||||
}
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(homeworkAssignments).values({
|
||||
id: assignmentId,
|
||||
sourceExamId: input.sourceExamId,
|
||||
title: input.title?.trim().length ? input.title.trim() : exam.title,
|
||||
description: input.description ?? null,
|
||||
structure: publish ? (exam.structure as unknown) : null,
|
||||
status: publish ? "published" : "draft",
|
||||
creatorId: ctx.userId,
|
||||
availableAt,
|
||||
dueAt,
|
||||
allowLate: input.allowLate ?? false,
|
||||
lateDueAt,
|
||||
maxAttempts: input.maxAttempts ?? 1,
|
||||
})
|
||||
const assignmentId = createId()
|
||||
const availableAt = input.availableAt ? new Date(input.availableAt) : null
|
||||
const dueAt = input.dueAt ? new Date(input.dueAt) : null
|
||||
const lateDueAt = input.lateDueAt ? new Date(input.lateDueAt) : null
|
||||
|
||||
if (publish && exam.questions.length > 0) {
|
||||
await tx.insert(homeworkAssignmentQuestions).values(
|
||||
exam.questions.map((q) => ({
|
||||
assignmentId,
|
||||
questionId: q.questionId,
|
||||
score: q.score ?? 0,
|
||||
order: q.order ?? 0,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
if (publish && targetStudentIds.length > 0) {
|
||||
await tx.insert(homeworkAssignmentTargets).values(
|
||||
targetStudentIds.map((studentId) => ({
|
||||
assignmentId,
|
||||
studentId,
|
||||
}))
|
||||
)
|
||||
}
|
||||
await createHomeworkAssignment({
|
||||
assignmentId,
|
||||
sourceExamId: input.sourceExamId,
|
||||
title: input.title?.trim().length ? input.title.trim() : exam.title,
|
||||
description: input.description ?? null,
|
||||
structure: exam.structure,
|
||||
status: publish ? "published" : "draft",
|
||||
creatorId: ctx.userId,
|
||||
availableAt,
|
||||
dueAt,
|
||||
allowLate: input.allowLate ?? false,
|
||||
lateDueAt,
|
||||
maxAttempts: input.maxAttempts ?? 1,
|
||||
publish,
|
||||
questions: exam.questions,
|
||||
targetStudentIds,
|
||||
})
|
||||
|
||||
revalidatePath("/teacher/homework/assignments")
|
||||
@@ -197,40 +150,14 @@ export async function startHomeworkSubmissionAction(
|
||||
const assignmentId = formData.get("assignmentId")
|
||||
if (typeof assignmentId !== "string" || assignmentId.length === 0) return { success: false, message: "Missing assignmentId" }
|
||||
|
||||
const assignment = await db.query.homeworkAssignments.findFirst({
|
||||
where: eq(homeworkAssignments.id, assignmentId),
|
||||
})
|
||||
if (!assignment) return { success: false, message: "Assignment not found" }
|
||||
if (assignment.status !== "published") return { success: false, message: "Assignment not available" }
|
||||
|
||||
const target = await db.query.homeworkAssignmentTargets.findFirst({
|
||||
where: and(eq(homeworkAssignmentTargets.assignmentId, assignmentId), eq(homeworkAssignmentTargets.studentId, ctx.userId)),
|
||||
})
|
||||
if (!target) return { success: false, message: "Not assigned" }
|
||||
|
||||
if (assignment.availableAt && assignment.availableAt > new Date()) return { success: false, message: "Not available yet" }
|
||||
|
||||
const [attemptRow] = await db
|
||||
.select({ c: count() })
|
||||
.from(homeworkSubmissions)
|
||||
.where(and(eq(homeworkSubmissions.assignmentId, assignmentId), eq(homeworkSubmissions.studentId, ctx.userId)))
|
||||
|
||||
const attemptNo = (attemptRow?.c ?? 0) + 1
|
||||
if (attemptNo > assignment.maxAttempts) return { success: false, message: "No attempts left" }
|
||||
|
||||
const submissionId = createId()
|
||||
await db.insert(homeworkSubmissions).values({
|
||||
id: submissionId,
|
||||
assignmentId,
|
||||
studentId: ctx.userId,
|
||||
attemptNo,
|
||||
status: "started",
|
||||
startedAt: new Date(),
|
||||
})
|
||||
const result = await startHomeworkSubmission(assignmentId, ctx.userId)
|
||||
if ("error" in result) {
|
||||
return { success: false, message: result.error }
|
||||
}
|
||||
|
||||
revalidatePath("/student/learning/assignments")
|
||||
|
||||
return { success: true, message: "Started", data: submissionId }
|
||||
return { success: true, message: "Started", data: result.submissionId }
|
||||
} catch (e) {
|
||||
if (e instanceof PermissionDeniedError) {
|
||||
return { success: false, message: e.message }
|
||||
@@ -252,35 +179,14 @@ export async function saveHomeworkAnswerAction(
|
||||
if (typeof submissionId !== "string" || submissionId.length === 0) return { success: false, message: "Missing submissionId" }
|
||||
if (typeof questionId !== "string" || questionId.length === 0) return { success: false, message: "Missing questionId" }
|
||||
|
||||
const submission = await db.query.homeworkSubmissions.findFirst({
|
||||
where: eq(homeworkSubmissions.id, submissionId),
|
||||
with: { assignment: true },
|
||||
})
|
||||
const submission = await getHomeworkSubmissionForPermission(submissionId)
|
||||
if (!submission) return { success: false, message: "Submission not found" }
|
||||
if (submission.studentId !== ctx.userId) return { success: false, message: "Unauthorized" }
|
||||
if (submission.status !== "started") return { success: false, message: "Submission is locked" }
|
||||
|
||||
const payload = typeof answerJson === "string" && answerJson.length > 0 ? JSON.parse(answerJson) : null
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
const existing = await tx.query.homeworkAnswers.findFirst({
|
||||
where: and(eq(homeworkAnswers.submissionId, submissionId), eq(homeworkAnswers.questionId, questionId)),
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
await tx
|
||||
.update(homeworkAnswers)
|
||||
.set({ answerContent: payload, updatedAt: new Date() })
|
||||
.where(eq(homeworkAnswers.id, existing.id))
|
||||
} else {
|
||||
await tx.insert(homeworkAnswers).values({
|
||||
id: createId(),
|
||||
submissionId,
|
||||
questionId,
|
||||
answerContent: payload,
|
||||
})
|
||||
}
|
||||
})
|
||||
await saveHomeworkAnswer(submissionId, questionId, payload)
|
||||
|
||||
return { success: true, message: "Saved", data: submissionId }
|
||||
} catch (e) {
|
||||
@@ -301,10 +207,7 @@ export async function submitHomeworkAction(
|
||||
const submissionId = formData.get("submissionId")
|
||||
if (typeof submissionId !== "string" || submissionId.length === 0) return { success: false, message: "Missing submissionId" }
|
||||
|
||||
const submission = await db.query.homeworkSubmissions.findFirst({
|
||||
where: eq(homeworkSubmissions.id, submissionId),
|
||||
with: { assignment: true },
|
||||
})
|
||||
const submission = await getHomeworkSubmissionForPermission(submissionId)
|
||||
if (!submission) return { success: false, message: "Submission not found" }
|
||||
if (submission.studentId !== ctx.userId) return { success: false, message: "Unauthorized" }
|
||||
if (submission.status !== "started") return { success: false, message: "Already submitted" }
|
||||
@@ -319,10 +222,7 @@ export async function submitHomeworkAction(
|
||||
|
||||
const isLate = Boolean(dueAt && now > dueAt)
|
||||
|
||||
await db
|
||||
.update(homeworkSubmissions)
|
||||
.set({ status: "submitted", submittedAt: now, isLate, updatedAt: now })
|
||||
.where(eq(homeworkSubmissions.id, submissionId))
|
||||
await markHomeworkSubmitted(submissionId, isLate)
|
||||
|
||||
revalidatePath("/teacher/homework/submissions")
|
||||
revalidatePath("/student/learning/assignments")
|
||||
@@ -359,20 +259,15 @@ export async function gradeHomeworkSubmissionAction(
|
||||
}
|
||||
|
||||
const { submissionId, answers } = parsed.data
|
||||
let totalScore = 0
|
||||
|
||||
for (const ans of answers) {
|
||||
await db
|
||||
.update(homeworkAnswers)
|
||||
.set({ score: ans.score, feedback: ans.feedback ?? null, updatedAt: new Date() })
|
||||
.where(eq(homeworkAnswers.id, ans.id))
|
||||
totalScore += ans.score
|
||||
}
|
||||
|
||||
await db
|
||||
.update(homeworkSubmissions)
|
||||
.set({ score: totalScore, status: "graded", updatedAt: new Date() })
|
||||
.where(eq(homeworkSubmissions.id, submissionId))
|
||||
await gradeHomeworkAnswers(
|
||||
submissionId,
|
||||
answers.map((ans) => ({
|
||||
id: ans.id,
|
||||
score: ans.score,
|
||||
feedback: ans.feedback ?? null,
|
||||
}))
|
||||
)
|
||||
|
||||
revalidatePath("/teacher/homework/submissions")
|
||||
|
||||
|
||||
317
src/modules/homework/data-access-write.ts
Normal file
317
src/modules/homework/data-access-write.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
import "server-only"
|
||||
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { and, count, eq } from "drizzle-orm"
|
||||
|
||||
import { db } from "@/shared/db"
|
||||
import {
|
||||
classes,
|
||||
classEnrollments,
|
||||
classSubjectTeachers,
|
||||
exams,
|
||||
homeworkAnswers,
|
||||
homeworkAssignmentQuestions,
|
||||
homeworkAssignmentTargets,
|
||||
homeworkAssignments,
|
||||
homeworkSubmissions,
|
||||
} from "@/shared/db/schema"
|
||||
import type { DataScope } from "@/shared/types/permissions"
|
||||
|
||||
// ---- Types ----
|
||||
|
||||
export type HomeworkExamQuestionData = {
|
||||
questionId: string
|
||||
score: number | null
|
||||
order: number | null
|
||||
}
|
||||
|
||||
export type HomeworkExamData = {
|
||||
id: string
|
||||
title: string
|
||||
subjectId: string | null
|
||||
structure: unknown
|
||||
questions: HomeworkExamQuestionData[]
|
||||
}
|
||||
|
||||
export type HomeworkSubmissionPermissionData = {
|
||||
id: string
|
||||
studentId: string
|
||||
status: string | null
|
||||
assignment: {
|
||||
dueAt: Date | null
|
||||
allowLate: boolean
|
||||
lateDueAt: Date | null
|
||||
}
|
||||
}
|
||||
|
||||
export type CreateHomeworkAssignmentData = {
|
||||
assignmentId: string
|
||||
sourceExamId: string
|
||||
title: string
|
||||
description: string | null
|
||||
structure: unknown
|
||||
status: string
|
||||
creatorId: string
|
||||
availableAt: Date | null
|
||||
dueAt: Date | null
|
||||
allowLate: boolean
|
||||
lateDueAt: Date | null
|
||||
maxAttempts: number
|
||||
publish: boolean
|
||||
questions: HomeworkExamQuestionData[]
|
||||
targetStudentIds: string[]
|
||||
}
|
||||
|
||||
// ---- Query helpers (for permission/validation in actions) ----
|
||||
|
||||
export const getClassTeacherById = async (
|
||||
classId: string
|
||||
): Promise<{ id: string; teacherId: string } | null> => {
|
||||
const [row] = await db
|
||||
.select({ id: classes.id, teacherId: classes.teacherId })
|
||||
.from(classes)
|
||||
.where(eq(classes.id, classId))
|
||||
.limit(1)
|
||||
return row ?? null
|
||||
}
|
||||
|
||||
export const getExamWithQuestionsForHomework = async (
|
||||
examId: string
|
||||
): Promise<HomeworkExamData | null> => {
|
||||
const exam = await db.query.exams.findFirst({
|
||||
where: eq(exams.id, examId),
|
||||
with: {
|
||||
questions: {
|
||||
orderBy: (examQuestions, { asc }) => [asc(examQuestions.order)],
|
||||
},
|
||||
},
|
||||
})
|
||||
if (!exam) return null
|
||||
return {
|
||||
id: exam.id,
|
||||
title: exam.title,
|
||||
subjectId: exam.subjectId,
|
||||
structure: exam.structure,
|
||||
questions: exam.questions.map((q) => ({
|
||||
questionId: q.questionId,
|
||||
score: q.score ?? null,
|
||||
order: q.order ?? null,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export const getTeacherAssignedSubjectIds = async (
|
||||
classId: string,
|
||||
teacherId: string
|
||||
): Promise<string[]> => {
|
||||
const rows = await db
|
||||
.select({ subjectId: classSubjectTeachers.subjectId })
|
||||
.from(classSubjectTeachers)
|
||||
.where(
|
||||
and(
|
||||
eq(classSubjectTeachers.classId, classId),
|
||||
eq(classSubjectTeachers.teacherId, teacherId)
|
||||
)
|
||||
)
|
||||
return rows.map((r) => r.subjectId)
|
||||
}
|
||||
|
||||
export const getActiveClassStudentIdsForHomework = async (
|
||||
classId: string,
|
||||
dataScope: DataScope,
|
||||
userId: string,
|
||||
classTeacherId: string
|
||||
): Promise<string[]> => {
|
||||
const classScope =
|
||||
dataScope.type === "all"
|
||||
? eq(classes.id, classId)
|
||||
: classTeacherId === userId
|
||||
? eq(classes.teacherId, userId)
|
||||
: eq(classes.id, classId)
|
||||
|
||||
const rows = await db
|
||||
.select({ studentId: classEnrollments.studentId })
|
||||
.from(classEnrollments)
|
||||
.innerJoin(classes, eq(classes.id, classEnrollments.classId))
|
||||
.where(
|
||||
and(
|
||||
eq(classEnrollments.classId, classId),
|
||||
eq(classEnrollments.status, "active"),
|
||||
classScope
|
||||
)
|
||||
)
|
||||
|
||||
return rows.map((r) => r.studentId)
|
||||
}
|
||||
|
||||
export const getHomeworkSubmissionForPermission = async (
|
||||
submissionId: string
|
||||
): Promise<HomeworkSubmissionPermissionData | null> => {
|
||||
const submission = await db.query.homeworkSubmissions.findFirst({
|
||||
where: eq(homeworkSubmissions.id, submissionId),
|
||||
with: { assignment: true },
|
||||
})
|
||||
if (!submission) return null
|
||||
return {
|
||||
id: submission.id,
|
||||
studentId: submission.studentId,
|
||||
status: submission.status,
|
||||
assignment: {
|
||||
dueAt: submission.assignment.dueAt,
|
||||
allowLate: submission.assignment.allowLate,
|
||||
lateDueAt: submission.assignment.lateDueAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Write functions ----
|
||||
|
||||
export const createHomeworkAssignment = async (
|
||||
input: CreateHomeworkAssignmentData
|
||||
): Promise<string> => {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(homeworkAssignments).values({
|
||||
id: input.assignmentId,
|
||||
sourceExamId: input.sourceExamId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
structure: input.publish ? input.structure : null,
|
||||
status: input.status,
|
||||
creatorId: input.creatorId,
|
||||
availableAt: input.availableAt,
|
||||
dueAt: input.dueAt,
|
||||
allowLate: input.allowLate,
|
||||
lateDueAt: input.lateDueAt,
|
||||
maxAttempts: input.maxAttempts,
|
||||
})
|
||||
|
||||
if (input.publish && input.questions.length > 0) {
|
||||
await tx.insert(homeworkAssignmentQuestions).values(
|
||||
input.questions.map((q) => ({
|
||||
assignmentId: input.assignmentId,
|
||||
questionId: q.questionId,
|
||||
score: q.score ?? 0,
|
||||
order: q.order ?? 0,
|
||||
}))
|
||||
)
|
||||
}
|
||||
|
||||
if (input.publish && input.targetStudentIds.length > 0) {
|
||||
await tx.insert(homeworkAssignmentTargets).values(
|
||||
input.targetStudentIds.map((studentId) => ({
|
||||
assignmentId: input.assignmentId,
|
||||
studentId,
|
||||
}))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return input.assignmentId
|
||||
}
|
||||
|
||||
export const startHomeworkSubmission = async (
|
||||
assignmentId: string,
|
||||
studentId: string
|
||||
): Promise<{ submissionId: string } | { error: string }> => {
|
||||
const assignment = await db.query.homeworkAssignments.findFirst({
|
||||
where: eq(homeworkAssignments.id, assignmentId),
|
||||
})
|
||||
if (!assignment) return { error: "Assignment not found" }
|
||||
if (assignment.status !== "published") return { error: "Assignment not available" }
|
||||
|
||||
const target = await db.query.homeworkAssignmentTargets.findFirst({
|
||||
where: and(
|
||||
eq(homeworkAssignmentTargets.assignmentId, assignmentId),
|
||||
eq(homeworkAssignmentTargets.studentId, studentId)
|
||||
),
|
||||
})
|
||||
if (!target) return { error: "Not assigned" }
|
||||
|
||||
if (assignment.availableAt && assignment.availableAt > new Date()) {
|
||||
return { error: "Not available yet" }
|
||||
}
|
||||
|
||||
const [attemptRow] = await db
|
||||
.select({ c: count() })
|
||||
.from(homeworkSubmissions)
|
||||
.where(
|
||||
and(
|
||||
eq(homeworkSubmissions.assignmentId, assignmentId),
|
||||
eq(homeworkSubmissions.studentId, studentId)
|
||||
)
|
||||
)
|
||||
|
||||
const attemptNo = (attemptRow?.c ?? 0) + 1
|
||||
if (attemptNo > assignment.maxAttempts) return { error: "No attempts left" }
|
||||
|
||||
const submissionId = createId()
|
||||
await db.insert(homeworkSubmissions).values({
|
||||
id: submissionId,
|
||||
assignmentId,
|
||||
studentId,
|
||||
attemptNo,
|
||||
status: "started",
|
||||
startedAt: new Date(),
|
||||
})
|
||||
|
||||
return { submissionId }
|
||||
}
|
||||
|
||||
export const saveHomeworkAnswer = async (
|
||||
submissionId: string,
|
||||
questionId: string,
|
||||
answerContent: unknown
|
||||
): Promise<void> => {
|
||||
await db.transaction(async (tx) => {
|
||||
const existing = await tx.query.homeworkAnswers.findFirst({
|
||||
where: and(
|
||||
eq(homeworkAnswers.submissionId, submissionId),
|
||||
eq(homeworkAnswers.questionId, questionId)
|
||||
),
|
||||
})
|
||||
|
||||
if (existing) {
|
||||
await tx
|
||||
.update(homeworkAnswers)
|
||||
.set({ answerContent, updatedAt: new Date() })
|
||||
.where(eq(homeworkAnswers.id, existing.id))
|
||||
} else {
|
||||
await tx.insert(homeworkAnswers).values({
|
||||
id: createId(),
|
||||
submissionId,
|
||||
questionId,
|
||||
answerContent,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const markHomeworkSubmitted = async (
|
||||
submissionId: string,
|
||||
isLate: boolean
|
||||
): Promise<void> => {
|
||||
const now = new Date()
|
||||
await db
|
||||
.update(homeworkSubmissions)
|
||||
.set({ status: "submitted", submittedAt: now, isLate, updatedAt: now })
|
||||
.where(eq(homeworkSubmissions.id, submissionId))
|
||||
}
|
||||
|
||||
export const gradeHomeworkAnswers = async (
|
||||
submissionId: string,
|
||||
answers: Array<{ id: string; score: number; feedback: string | null }>
|
||||
): Promise<void> => {
|
||||
let totalScore = 0
|
||||
for (const ans of answers) {
|
||||
await db
|
||||
.update(homeworkAnswers)
|
||||
.set({ score: ans.score, feedback: ans.feedback, updatedAt: new Date() })
|
||||
.where(eq(homeworkAnswers.id, ans.id))
|
||||
totalScore += ans.score
|
||||
}
|
||||
|
||||
await db
|
||||
.update(homeworkSubmissions)
|
||||
.set({ score: totalScore, status: "graded", updatedAt: new Date() })
|
||||
.where(eq(homeworkSubmissions.id, submissionId))
|
||||
}
|
||||
Reference in New Issue
Block a user