sync-docs-and-fixes
This commit is contained in:
@@ -1,64 +1,64 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { headers } from "next/headers"
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { and, count, eq } from "drizzle-orm"
|
||||
import { and, count, eq, inArray } from "drizzle-orm"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import { db } from "@/shared/db"
|
||||
import {
|
||||
classes,
|
||||
classEnrollments,
|
||||
classSubjectTeachers,
|
||||
exams,
|
||||
homeworkAnswers,
|
||||
homeworkAssignmentQuestions,
|
||||
homeworkAssignmentTargets,
|
||||
homeworkAssignments,
|
||||
homeworkSubmissions,
|
||||
roles,
|
||||
users,
|
||||
usersToRoles,
|
||||
} from "@/shared/db/schema"
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
|
||||
import { CreateHomeworkAssignmentSchema, GradeHomeworkSchema } from "./schema"
|
||||
|
||||
type CurrentUser = { id: string; role: "admin" | "teacher" | "student" }
|
||||
type TeacherRole = "admin" | "teacher"
|
||||
type StudentRole = "student"
|
||||
|
||||
async function getCurrentUser() {
|
||||
const ref = (await headers()).get("referer") || ""
|
||||
const roleHint: CurrentUser["role"] = ref.includes("/admin/")
|
||||
? "admin"
|
||||
: ref.includes("/student/")
|
||||
? "student"
|
||||
: ref.includes("/teacher/")
|
||||
? "teacher"
|
||||
: "teacher"
|
||||
|
||||
const byRole = await db.query.users.findFirst({
|
||||
where: eq(users.role, roleHint),
|
||||
orderBy: (u, { asc }) => [asc(u.createdAt)],
|
||||
})
|
||||
|
||||
if (byRole) return { id: byRole.id, role: roleHint }
|
||||
|
||||
const anyUser = await db.query.users.findFirst({
|
||||
orderBy: (u, { asc }) => [asc(u.createdAt)],
|
||||
})
|
||||
|
||||
if (anyUser) return { id: anyUser.id, role: roleHint }
|
||||
|
||||
return { id: "user_teacher_math", role: roleHint }
|
||||
const getSessionUserId = async (): Promise<string | null> => {
|
||||
const session = await auth()
|
||||
const userId = String(session?.user?.id ?? "").trim()
|
||||
return userId.length > 0 ? userId : null
|
||||
}
|
||||
|
||||
async function ensureTeacher() {
|
||||
const user = await getCurrentUser()
|
||||
if (!user || (user.role !== "teacher" && user.role !== "admin")) throw new Error("Unauthorized")
|
||||
return user
|
||||
async function ensureTeacher(): Promise<{ id: string; role: TeacherRole }> {
|
||||
const userId = await getSessionUserId()
|
||||
if (!userId) throw new Error("Unauthorized")
|
||||
const [row] = await db
|
||||
.select({ id: users.id, role: roles.name })
|
||||
.from(users)
|
||||
.innerJoin(usersToRoles, eq(usersToRoles.userId, users.id))
|
||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
||||
.where(and(eq(users.id, userId), inArray(roles.name, ["teacher", "admin"])))
|
||||
.limit(1)
|
||||
if (!row) throw new Error("Unauthorized")
|
||||
return { id: row.id, role: row.role as TeacherRole }
|
||||
}
|
||||
|
||||
async function ensureStudent() {
|
||||
const user = await getCurrentUser()
|
||||
if (!user || user.role !== "student") throw new Error("Unauthorized")
|
||||
return user
|
||||
async function ensureStudent(): Promise<{ id: string; role: StudentRole }> {
|
||||
const userId = await getSessionUserId()
|
||||
if (!userId) throw new Error("Unauthorized")
|
||||
const [row] = await db
|
||||
.select({ id: users.id })
|
||||
.from(users)
|
||||
.innerJoin(usersToRoles, eq(usersToRoles.userId, users.id))
|
||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
||||
.where(and(eq(users.id, userId), eq(roles.name, "student")))
|
||||
.limit(1)
|
||||
if (!row) throw new Error("Unauthorized")
|
||||
return { id: row.id, role: "student" }
|
||||
}
|
||||
|
||||
const parseStudentIds = (raw: string): string[] => {
|
||||
@@ -108,12 +108,12 @@ export async function createHomeworkAssignmentAction(
|
||||
const input = parsed.data
|
||||
const publish = input.publish ?? true
|
||||
|
||||
const [ownedClass] = await db
|
||||
.select({ id: classes.id })
|
||||
const [classRow] = await db
|
||||
.select({ id: classes.id, teacherId: classes.teacherId })
|
||||
.from(classes)
|
||||
.where(user.role === "admin" ? eq(classes.id, input.classId) : and(eq(classes.id, input.classId), eq(classes.teacherId, user.id)))
|
||||
.where(eq(classes.id, input.classId))
|
||||
.limit(1)
|
||||
if (!ownedClass) return { success: false, message: "Class not found" }
|
||||
if (!classRow) return { success: false, message: "Class not found" }
|
||||
|
||||
const exam = await db.query.exams.findFirst({
|
||||
where: eq(exams.id, input.sourceExamId),
|
||||
@@ -126,23 +126,43 @@ export async function createHomeworkAssignmentAction(
|
||||
|
||||
if (!exam) return { success: false, message: "Exam not found" }
|
||||
|
||||
if (user.role !== "admin" && classRow.teacherId !== user.id) {
|
||||
const assignedSubjectRows = await db
|
||||
.select({ subjectId: classSubjectTeachers.subjectId })
|
||||
.from(classSubjectTeachers)
|
||||
.where(and(eq(classSubjectTeachers.classId, input.classId), eq(classSubjectTeachers.teacherId, user.id)))
|
||||
if (assignedSubjectRows.length === 0) {
|
||||
return { success: false, message: "Not assigned to this class" }
|
||||
}
|
||||
const assignedSubjectIds = new Set(assignedSubjectRows.map((r) => r.subjectId))
|
||||
if (!exam.subjectId) {
|
||||
return { success: false, message: "Exam subject not set" }
|
||||
}
|
||||
if (!assignedSubjectIds.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 =
|
||||
user.role === "admin"
|
||||
? eq(classes.id, input.classId)
|
||||
: classRow.teacherId === user.id
|
||||
? eq(classes.teacherId, user.id)
|
||||
: 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"),
|
||||
user.role === "admin" ? eq(classes.id, input.classId) : eq(classes.teacherId, user.id)
|
||||
)
|
||||
and(eq(classEnrollments.classId, input.classId), eq(classEnrollments.status, "active"), classScope)
|
||||
)
|
||||
).map((r) => r.studentId)
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { Checkbox } from "@/shared/components/ui/checkbox"
|
||||
import { Label } from "@/shared/components/ui/label"
|
||||
import { Textarea } from "@/shared/components/ui/textarea"
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import { Separator } from "@/shared/components/ui/separator"
|
||||
import { Clock, CheckCircle2, Save, FileText } from "lucide-react"
|
||||
|
||||
import type { StudentHomeworkTakeData } from "../types"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/sha
|
||||
import { Checkbox } from "@/shared/components/ui/checkbox"
|
||||
import { Label } from "@/shared/components/ui/label"
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import { CheckCircle2, FileText, ChevronLeft } from "lucide-react"
|
||||
import { FileText, ChevronLeft } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
|
||||
import type { StudentHomeworkTakeData } from "../types"
|
||||
@@ -57,7 +57,6 @@ type HomeworkReviewViewProps = {
|
||||
export function HomeworkReviewView({ initialData }: HomeworkReviewViewProps) {
|
||||
const submissionStatus = initialData.submission?.status ?? "not_started"
|
||||
const isGraded = submissionStatus === "graded"
|
||||
const isSubmitted = submissionStatus === "submitted"
|
||||
|
||||
const answersByQuestionId = useMemo(() => {
|
||||
const map = new Map<string, { answer: unknown }>()
|
||||
|
||||
@@ -3,6 +3,7 @@ import "server-only"
|
||||
import { cache } from "react"
|
||||
import { and, count, desc, eq, inArray, isNull, lte, or, sql } from "drizzle-orm"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
import { db } from "@/shared/db"
|
||||
import {
|
||||
classEnrollments,
|
||||
@@ -11,7 +12,9 @@ import {
|
||||
homeworkAssignmentTargets,
|
||||
homeworkAssignments,
|
||||
homeworkSubmissions,
|
||||
roles,
|
||||
users,
|
||||
usersToRoles,
|
||||
} from "@/shared/db/schema"
|
||||
|
||||
import type {
|
||||
@@ -550,17 +553,20 @@ export const getHomeworkSubmissionDetails = cache(async (submissionId: string):
|
||||
})
|
||||
|
||||
export const getDemoStudentUser = cache(async (): Promise<{ id: string; name: string } | null> => {
|
||||
const student = await db.query.users.findFirst({
|
||||
where: eq(users.role, "student"),
|
||||
orderBy: (u, { asc }) => [asc(u.createdAt)],
|
||||
})
|
||||
if (student) return { id: student.id, name: student.name || "Student" }
|
||||
const session = await auth()
|
||||
const userId = String(session?.user?.id ?? "").trim()
|
||||
if (!userId) return null
|
||||
|
||||
const anyUser = await db.query.users.findFirst({
|
||||
orderBy: (u, { asc }) => [asc(u.createdAt)],
|
||||
})
|
||||
if (!anyUser) return null
|
||||
return { id: anyUser.id, name: anyUser.name || "User" }
|
||||
const [student] = await db
|
||||
.select({ id: users.id, name: users.name })
|
||||
.from(users)
|
||||
.innerJoin(usersToRoles, eq(usersToRoles.userId, users.id))
|
||||
.innerJoin(roles, eq(usersToRoles.roleId, roles.id))
|
||||
.where(and(eq(users.id, userId), eq(roles.name, "student")))
|
||||
.limit(1)
|
||||
|
||||
if (!student) return null
|
||||
return { id: student.id, name: student.name || "Student" }
|
||||
})
|
||||
|
||||
const toStudentProgressStatus = (v: string | null | undefined): StudentHomeworkProgressStatus => {
|
||||
@@ -592,19 +598,23 @@ export const getStudentHomeworkAssignments = cache(async (studentId: string): Pr
|
||||
const assignmentIds = assignments.map((a) => a.id)
|
||||
const submissions = await db.query.homeworkSubmissions.findMany({
|
||||
where: and(eq(homeworkSubmissions.studentId, studentId), inArray(homeworkSubmissions.assignmentId, assignmentIds)),
|
||||
orderBy: [desc(homeworkSubmissions.createdAt)],
|
||||
orderBy: [desc(homeworkSubmissions.updatedAt)],
|
||||
})
|
||||
|
||||
const attemptsByAssignmentId = new Map<string, number>()
|
||||
const latestByAssignmentId = new Map<string, (typeof submissions)[number]>()
|
||||
const latestSubmittedByAssignmentId = new Map<string, (typeof submissions)[number]>()
|
||||
|
||||
for (const s of submissions) {
|
||||
attemptsByAssignmentId.set(s.assignmentId, (attemptsByAssignmentId.get(s.assignmentId) ?? 0) + 1)
|
||||
if (!latestByAssignmentId.has(s.assignmentId)) latestByAssignmentId.set(s.assignmentId, s)
|
||||
if (s.status === "submitted" || s.status === "graded") {
|
||||
if (!latestSubmittedByAssignmentId.has(s.assignmentId)) latestSubmittedByAssignmentId.set(s.assignmentId, s)
|
||||
}
|
||||
}
|
||||
|
||||
return assignments.map((a) => {
|
||||
const latest = latestByAssignmentId.get(a.id) ?? null
|
||||
const latest = latestSubmittedByAssignmentId.get(a.id) ?? latestByAssignmentId.get(a.id) ?? null
|
||||
const attemptsUsed = attemptsByAssignmentId.get(a.id) ?? 0
|
||||
|
||||
const item: StudentHomeworkAssignmentListItem = {
|
||||
|
||||
Reference in New Issue
Block a user