diff --git a/prisma/migrations/20260515033528_rename_guest_role_to_student/migration.sql b/prisma/migrations/20260515033528_rename_guest_role_to_student/migration.sql new file mode 100644 index 0000000..0c3cf96 --- /dev/null +++ b/prisma/migrations/20260515033528_rename_guest_role_to_student/migration.sql @@ -0,0 +1,19 @@ +/* + Warnings: + + - The values [GUEST] on the enum `Role` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "Role_new" AS ENUM ('ADMIN', 'STUDENT', 'TEACHER'); +ALTER TABLE "User" ALTER COLUMN "role" DROP DEFAULT; +ALTER TABLE "User" ALTER COLUMN "role" TYPE "Role_new" USING ("role"::text::"Role_new"); +ALTER TYPE "Role" RENAME TO "Role_old"; +ALTER TYPE "Role_new" RENAME TO "Role"; +DROP TYPE "Role_old"; +ALTER TABLE "User" ALTER COLUMN "role" SET DEFAULT 'STUDENT'; +COMMIT; + +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "role" SET DEFAULT 'STUDENT'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 185c336..2769a32 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,7 @@ generator client { enum Role { ADMIN - GUEST + STUDENT TEACHER } @@ -90,7 +90,7 @@ model User { password String? emailVerified DateTime? image String? - role Role @default(GUEST) + role Role @default(STUDENT) accounts Account[] sessions Session[] diff --git a/prisma/seed.ts b/prisma/seed.ts index 2b39798..fed65f2 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -2999,12 +2999,12 @@ export async function main() { where: { email }, update: { name: `学生${index + 1}`, - role: "GUEST", + role: "STUDENT", }, create: { name: `学生${index + 1}`, email, - role: "GUEST", + role: "STUDENT", }, }) ) diff --git a/src/app/(protected)/dashboard/actions/course-auth.ts b/src/app/(protected)/dashboard/actions/course-auth.ts index a7957f3..08e8199 100644 --- a/src/app/(protected)/dashboard/actions/course-auth.ts +++ b/src/app/(protected)/dashboard/actions/course-auth.ts @@ -41,7 +41,7 @@ export function assertTeacherOrAdmin(actor: AuthenticatedActor) { } export function assertStudent(actor: AuthenticatedActor) { - if (actor.role !== "GUEST") { + if (actor.role !== "STUDENT") { throw new Error("仅学生可访问"); } } diff --git a/src/app/(protected)/dashboard/actions/teacher-courses.ts b/src/app/(protected)/dashboard/actions/teacher-courses.ts index 65499c2..780c6dc 100644 --- a/src/app/(protected)/dashboard/actions/teacher-courses.ts +++ b/src/app/(protected)/dashboard/actions/teacher-courses.ts @@ -130,7 +130,7 @@ export async function enrollStudents(courseId: string, studentIds: string[]) { const students = await prisma.user.findMany({ where: { id: { in: uniqueStudentIds }, - role: "GUEST", + role: "STUDENT", }, select: { id: true }, }); @@ -197,7 +197,7 @@ export async function listAvailableStudents() { assertTeacherOrAdmin(actor); return prisma.user.findMany({ - where: { role: "GUEST" }, + where: { role: "STUDENT" }, orderBy: { createdAt: "desc" }, select: { id: true, diff --git a/src/app/(protected)/dashboard/layout.tsx b/src/app/(protected)/dashboard/layout.tsx index e403e75..d64703f 100644 --- a/src/app/(protected)/dashboard/layout.tsx +++ b/src/app/(protected)/dashboard/layout.tsx @@ -47,16 +47,16 @@ export default async function Layout({ children }: LayoutProps) { return ; case "TEACHER": return ; - case "GUEST": + case "STUDENT": default: - // 学生(GUEST)需要查询错题数据 + // 学生(STUDENT)需要查询错题数据 return ; } }; // 只有学生才需要查询错题数据 let wrongProblemsData: WrongProblem[] = []; - if (fullUser.role === "GUEST") { + if (fullUser.role === "STUDENT") { // 查询未完成(未AC)题目的最新一次提交 const wrongProblems = await prisma.problem.findMany({ where: { @@ -98,7 +98,7 @@ export default async function Layout({ children }: LayoutProps) { return ( - {fullUser.role === "GUEST" ? ( + {fullUser.role === "STUDENT" ? ( ) : ( renderSidebar() diff --git a/src/app/(protected)/dashboard/management/profile/page.tsx b/src/app/(protected)/dashboard/management/profile/page.tsx index 9d6e319..c31556b 100644 --- a/src/app/(protected)/dashboard/management/profile/page.tsx +++ b/src/app/(protected)/dashboard/management/profile/page.tsx @@ -12,7 +12,7 @@ interface User { email: string; emailVerified?: Date | null; image: string | null; - role: "GUEST" | "USER" | "ADMIN" | "TEACHER"; + role: "STUDENT" | "ADMIN" | "TEACHER"; createdAt: Date; updatedAt: Date; } diff --git a/src/app/(protected)/dashboard/page.tsx b/src/app/(protected)/dashboard/page.tsx index 95a5037..a24c98c 100644 --- a/src/app/(protected)/dashboard/page.tsx +++ b/src/app/(protected)/dashboard/page.tsx @@ -94,7 +94,7 @@ export default async function DashboardPage() { // 教师统计 const [totalStudents, totalProblems, totalSubmissions, recentSubmissions] = await Promise.all([ - prisma.user.count({ where: { role: "GUEST" } }), + prisma.user.count({ where: { role: "STUDENT" } }), prisma.problem.count({ where: { isPublished: true } }), prisma.submission.count(), prisma.submission.findMany({ @@ -204,8 +204,8 @@ export default async function DashboardPage() { icon: Target, }, { - label: "用户管理", - href: "/dashboard/usermanagement/guest", + label: "学生管理", + href: "/dashboard/usermanagement/student", icon: Users, }, { @@ -246,8 +246,8 @@ export default async function DashboardPage() { ], actions: [ { - label: "用户管理", - href: "/dashboard/usermanagement/guest", + label: "学生管理", + href: "/dashboard/usermanagement/student", icon: Users, }, { @@ -311,7 +311,7 @@ export default async function DashboardPage() { const config = getRoleConfig(); const completionRate = - fullUser.role === "GUEST" + fullUser.role === "STUDENT" ? (stats.totalProblems || 0) > 0 ? ((stats.completedProblems || 0) / (stats.totalProblems || 1)) * 100 : 0 @@ -349,7 +349,7 @@ export default async function DashboardPage() { {/* 学生进度条 */} - {fullUser.role === "GUEST" && ( + {fullUser.role === "STUDENT" && ( diff --git a/src/app/(protected)/dashboard/student/courses/layout.tsx b/src/app/(protected)/dashboard/student/courses/layout.tsx index 9e42eda..3c1023f 100644 --- a/src/app/(protected)/dashboard/student/courses/layout.tsx +++ b/src/app/(protected)/dashboard/student/courses/layout.tsx @@ -5,5 +5,5 @@ export default async function StudentCoursesLayout({ }: { children: React.ReactNode; }) { - return {children}; + return {children}; } diff --git a/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts b/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts index 16aa684..1441fc1 100644 --- a/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts +++ b/src/app/(protected)/dashboard/usermanagement/actions/userActions.ts @@ -6,10 +6,10 @@ import { Role } from "@/generated/client"; import { revalidatePath } from "next/cache"; import type { User } from "@/generated/client"; -type UserType = "admin" | "teacher" | "guest"; +type ResourceType = "admin" | "teacher" | "student"; export async function createUser( - userType: UserType, + resourceType: ResourceType, data: Omit & { password?: string } ) { let password = data.password; @@ -17,13 +17,13 @@ export async function createUser( password = await bcrypt.hash(password, 10); } - const role = userType.toUpperCase() as Role; + const role = resourceType.toUpperCase() as Role; await prisma.user.create({ data: { ...data, password, role } }); - revalidatePath(`/usermanagement/${userType}`); + revalidatePath(`/usermanagement/${resourceType}`); } export async function updateUser( - userType: UserType, + resourceType: ResourceType, id: string, data: Partial> ) { @@ -38,10 +38,10 @@ export async function updateUser( } await prisma.user.update({ where: { id }, data: updateData }); - revalidatePath(`/usermanagement/${userType}`); + revalidatePath(`/usermanagement/${resourceType}`); } -export async function deleteUser(userType: UserType, id: string) { +export async function deleteUser(resourceType: ResourceType, id: string) { await prisma.user.delete({ where: { id } }); - revalidatePath(`/usermanagement/${userType}`); + revalidatePath(`/usermanagement/${resourceType}`); } diff --git a/src/app/(protected)/dashboard/usermanagement/admin/page.tsx b/src/app/(protected)/dashboard/usermanagement/admin/page.tsx index 96065e8..1743039 100644 --- a/src/app/(protected)/dashboard/usermanagement/admin/page.tsx +++ b/src/app/(protected)/dashboard/usermanagement/admin/page.tsx @@ -2,5 +2,5 @@ import { adminConfig } from "@/features/user-management/config/admin"; import GenericPage from "@/features/user-management/components/generic-page"; export default function AdminPage() { - return ; + return ; } diff --git a/src/app/(protected)/dashboard/usermanagement/guest/page.tsx b/src/app/(protected)/dashboard/usermanagement/guest/page.tsx deleted file mode 100644 index 3aa6a30..0000000 --- a/src/app/(protected)/dashboard/usermanagement/guest/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { guestConfig } from "@/features/user-management/config/guest"; -import GenericPage from "@/features/user-management/components/generic-page"; - -export default function GuestPage() { - return ; -} diff --git a/src/app/(protected)/dashboard/usermanagement/problem/page.tsx b/src/app/(protected)/dashboard/usermanagement/problem/page.tsx index d61ac67..da83c85 100644 --- a/src/app/(protected)/dashboard/usermanagement/problem/page.tsx +++ b/src/app/(protected)/dashboard/usermanagement/problem/page.tsx @@ -2,5 +2,5 @@ import { problemConfig } from "@/features/user-management/config/problem"; import GenericPage from "@/features/user-management/components/generic-page"; export default function ProblemPage() { - return ; + return ; } diff --git a/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx b/src/app/(protected)/dashboard/usermanagement/student/layout.tsx similarity index 84% rename from src/app/(protected)/dashboard/usermanagement/guest/layout.tsx rename to src/app/(protected)/dashboard/usermanagement/student/layout.tsx index 1c4b421..4d31314 100644 --- a/src/app/(protected)/dashboard/usermanagement/guest/layout.tsx +++ b/src/app/(protected)/dashboard/usermanagement/student/layout.tsx @@ -1,6 +1,6 @@ import GenericLayout from "../components/GenericLayout"; -export default function GuestLayout({ +export default function StudentLayout({ children, }: { children: React.ReactNode; diff --git a/src/app/(protected)/dashboard/usermanagement/student/page.tsx b/src/app/(protected)/dashboard/usermanagement/student/page.tsx new file mode 100644 index 0000000..571bed7 --- /dev/null +++ b/src/app/(protected)/dashboard/usermanagement/student/page.tsx @@ -0,0 +1,6 @@ +import { studentConfig } from "@/features/user-management/config/student"; +import GenericPage from "@/features/user-management/components/generic-page"; + +export default function StudentPage() { + return ; +} diff --git a/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx b/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx index 26393b9..c57c832 100644 --- a/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx +++ b/src/app/(protected)/dashboard/usermanagement/teacher/page.tsx @@ -2,5 +2,5 @@ import { teacherConfig } from "@/features/user-management/config/teacher"; import GenericPage from "@/features/user-management/components/generic-page"; export default function TeacherPage() { - return ; + return ; } diff --git a/src/app/(protected)/layout.tsx b/src/app/(protected)/layout.tsx index 078b0c1..670eab8 100644 --- a/src/app/(protected)/layout.tsx +++ b/src/app/(protected)/layout.tsx @@ -6,7 +6,7 @@ interface LayoutProps { const Layout = ({ children }: LayoutProps) => { return ( - + {children} ); diff --git a/src/app/actions/judge.ts b/src/app/actions/judge.ts index 83af37e..3e60a67 100644 --- a/src/app/actions/judge.ts +++ b/src/app/actions/judge.ts @@ -84,7 +84,7 @@ export const judge = async ( const canAccessAssignment = actor.role === "ADMIN" || (actor.role === "TEACHER" && isTeacherOwner) || - (actor.role === "GUEST" && isStudentEnrolled); + (actor.role === "STUDENT" && isStudentEnrolled); if (!canAccessAssignment) { await createSystemErrorSubmission("No permission for assignment", { @@ -100,7 +100,7 @@ export const judge = async ( return Status.SE; } - if (!assignment.published && actor.role === "GUEST") { + if (!assignment.published && actor.role === "STUDENT") { await createSystemErrorSubmission("Assignment is not published", { assignmentId: assignment.id, }); diff --git a/src/components/dynamic-breadcrumb.tsx b/src/components/dynamic-breadcrumb.tsx index 9cec3b3..d8408a4 100644 --- a/src/components/dynamic-breadcrumb.tsx +++ b/src/components/dynamic-breadcrumb.tsx @@ -44,7 +44,7 @@ export function DynamicBreadcrumb() { admin: "管理后台", teacher: "教师平台", student: "学生平台", - usermanagement: "用户管理", + usermanagement: "账号管理", courses: "课程", assignments: "作业", userdashboard: "用户仪表板", diff --git a/src/components/sidebar/admin-sidebar.tsx b/src/components/sidebar/admin-sidebar.tsx index f6c739c..e3302e4 100644 --- a/src/components/sidebar/admin-sidebar.tsx +++ b/src/components/sidebar/admin-sidebar.tsx @@ -26,7 +26,7 @@ const adminData = { isActive: true, items: [ { title: "管理员管理", url: "/dashboard/usermanagement/admin" }, - { title: "用户管理", url: "/dashboard/usermanagement/guest" }, + { title: "学生管理", url: "/dashboard/usermanagement/student" }, { title: "教师管理", url: "/dashboard/usermanagement/teacher" }, { title: "题目管理", url: "/dashboard/usermanagement/problem" }, ], diff --git a/src/components/sidebar/teacher-sidebar.tsx b/src/components/sidebar/teacher-sidebar.tsx index 95241ba..347522c 100644 --- a/src/components/sidebar/teacher-sidebar.tsx +++ b/src/components/sidebar/teacher-sidebar.tsx @@ -26,8 +26,8 @@ const data = { isActive: true, items: [ { - title: "用户管理", - url: "/dashboard/usermanagement/guest", + title: "学生管理", + url: "/dashboard/usermanagement/student", }, { title: "题目管理", diff --git a/src/features/user-management/components/generic-page.tsx b/src/features/user-management/components/generic-page.tsx index 090c63e..79f029b 100644 --- a/src/features/user-management/components/generic-page.tsx +++ b/src/features/user-management/components/generic-page.tsx @@ -5,19 +5,19 @@ import { UserConfig } from "./user-table"; import type { User, Problem } from "@/generated/client"; interface GenericPageProps { - userType: "admin" | "teacher" | "guest" | "problem"; + resourceType: "admin" | "teacher" | "student" | "problem"; config: UserConfig; } export default async function GenericPage({ - userType, + resourceType, config, }: GenericPageProps) { - if (userType === "problem") { + if (resourceType === "problem") { const data: Problem[] = await prisma.problem.findMany({}); return ; } else { - const role = userType.toUpperCase() as Role; + const role = resourceType.toUpperCase() as Role; const data: User[] = await prisma.user.findMany({ where: { role } }); return ; } diff --git a/src/features/user-management/components/user-table.tsx b/src/features/user-management/components/user-table.tsx index 8fb8cba..fbe24c3 100644 --- a/src/features/user-management/components/user-table.tsx +++ b/src/features/user-management/components/user-table.tsx @@ -78,7 +78,7 @@ import { } from "@/app/(protected)/dashboard/usermanagement/actions/problemActions"; export interface UserConfig { - userType: string; + resourceType: string; title: string; apiPath: string; columns: Array<{ @@ -154,7 +154,7 @@ const addProblemSchema = z.object({ }); export function UserTable(props: UserTableProps) { - const isProblem = props.config.userType === "problem"; + const isProblem = props.config.resourceType === "problem"; const router = useRouter(); const problemData = isProblem ? (props.data as Problem[]) : undefined; @@ -324,7 +324,7 @@ export function UserTable(props: UserTableProps) { createdAt: "", image: null, emailVerified: null, - role: Role.GUEST, + role: Role.STUDENT, }, }); React.useEffect(() => { @@ -336,7 +336,7 @@ export function UserTable(props: UserTableProps) { createdAt: "", image: null, emailVerified: null, - role: Role.GUEST, + role: Role.STUDENT, }); } }, [open, form]); @@ -354,19 +354,19 @@ export function UserTable(props: UserTableProps) { ...data, image: data.image ?? null, emailVerified: data.emailVerified ?? null, - role: data.role ?? Role.GUEST, + role: data.role ?? Role.STUDENT, }; if (!submitData.name) submitData.name = ""; if (!submitData.createdAt) submitData.createdAt = new Date().toISOString(); else submitData.createdAt = new Date(submitData.createdAt).toISOString(); - if (props.config.userType === "admin") + if (props.config.resourceType === "admin") await createUser("admin", submitData); - else if (props.config.userType === "teacher") + else if (props.config.resourceType === "teacher") await createUser("teacher", submitData); - else if (props.config.userType === "guest") - await createUser("guest", submitData); + else if (props.config.resourceType === "student") + await createUser("student", submitData); onOpenChange(false); toast.success("添加成功", { duration: 1500 }); router.refresh(); @@ -610,7 +610,7 @@ export function UserTable(props: UserTableProps) { name: user.name ?? "", email: user.email ?? "", password: "", - role: user.role ?? Role.GUEST, + role: user.role ?? Role.STUDENT, createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : "", @@ -625,7 +625,7 @@ export function UserTable(props: UserTableProps) { name: user.name ?? "", email: user.email ?? "", password: "", - role: user.role ?? Role.GUEST, + role: user.role ?? Role.STUDENT, createdAt: user.createdAt ? new Date(user.createdAt).toISOString().slice(0, 16) : "", @@ -644,15 +644,15 @@ export function UserTable(props: UserTableProps) { : new Date().toISOString(), image: data.image ?? null, emailVerified: data.emailVerified ?? null, - role: data.role ?? Role.GUEST, + role: data.role ?? Role.STUDENT, }; const id = typeof submitData.id === "string" ? submitData.id : ""; - if (props.config.userType === "admin") + if (props.config.resourceType === "admin") await updateUser("admin", id, submitData); - else if (props.config.userType === "teacher") + else if (props.config.resourceType === "teacher") await updateUser("teacher", id, submitData); - else if (props.config.userType === "guest") - await updateUser("guest", id, submitData); + else if (props.config.resourceType === "student") + await updateUser("student", id, submitData); onOpenChange(false); toast.success("修改成功", { duration: 1500 }); } catch { @@ -710,7 +710,7 @@ export function UserTable(props: UserTableProps) { ))} {/* 编辑时显示角色选择 */} - {props.config.userType !== "problem" && ( + {props.config.resourceType !== "problem" && (