Fix all Server Actions imported from dynamic route paths

All admin and student Client Components were importing Server Actions
from paths with dynamic segments ([courseId], [moduleId], [lessonId], [slug]).
This caused "Cannot access toStringTag on the server" RSC crash.

Consolidated all Server Actions into static files under src/lib/actions/:
- course-actions.ts  (modules + enrollment)
- module-actions.ts  (lessons + reorder + move)
- user-actions.ts    (bulk grant / revoke)
- student-actions.ts (progress + homework + comments)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 14:05:26 +05:00
parent 9eb21e3ab4
commit 5dfa79d357
11 changed files with 410 additions and 7 deletions
+152
View File
@@ -0,0 +1,152 @@
"use server";
import { prisma } from "@/lib/prisma";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { revalidatePath } from "next/cache";
import { sendHomeworkSubmittedEmail } from "@/lib/email";
// ── Lesson Progress ───────────────────────────────────────────────────────────
export async function toggleLessonProgress(lessonId: string, slug: string) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
const existing = await prisma.lessonProgress.findUnique({
where: { userId_lessonId: { userId: session.user.id, lessonId } },
});
if (existing) {
await prisma.lessonProgress.delete({
where: { userId_lessonId: { userId: session.user.id, lessonId } },
});
} else {
await prisma.lessonProgress.create({
data: { userId: session.user.id, lessonId },
});
}
revalidatePath(`/courses/${slug}/lessons/${lessonId}`);
revalidatePath(`/courses/${slug}`);
revalidatePath("/dashboard");
}
// ── Homework ──────────────────────────────────────────────────────────────────
interface HomeworkFile {
name: string;
url: string;
size: number;
}
export async function submitHomework(
homeworkId: string,
slug: string,
lessonId: string,
text: string,
files: HomeworkFile[],
audioUrl?: string | null
) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
const existing = await prisma.homeworkSubmission.findFirst({
where: { homeworkId, userId: session.user.id },
include: { feedbacks: true },
});
if (existing?.feedbacks && existing.feedbacks.length > 0) {
throw new Error("Работа уже проверена");
}
let submissionId: string;
if (existing) {
const updated = await prisma.homeworkSubmission.update({
where: { id: existing.id },
data: { text, files: files as object[], audioUrl: audioUrl ?? null, submittedAt: new Date() },
});
submissionId = updated.id;
} else {
const created = await prisma.homeworkSubmission.create({
data: { homeworkId, userId: session.user.id, text, files: files as object[], audioUrl: audioUrl ?? null },
});
submissionId = created.id;
const [lesson, admins] = await Promise.all([
prisma.homework.findUnique({
where: { id: homeworkId },
include: { lesson: { select: { title: true } } },
}),
prisma.user.findMany({
where: { role: { in: ["admin", "curator"] } },
select: { email: true, name: true },
}),
]);
if (lesson) {
await Promise.all(
admins.map((a) =>
sendHomeworkSubmittedEmail(a.email, a.name, session.user.name, lesson.lesson.title, submissionId)
)
);
}
}
revalidatePath(`/courses/${slug}/lessons/${lessonId}`);
}
// ── Comments ──────────────────────────────────────────────────────────────────
export async function addComment(lessonId: string, slug: string, text: string) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
const trimmed = text.trim();
if (!trimmed || trimmed.length > 2000) throw new Error("Invalid text");
const lesson = await prisma.lesson.findUnique({
where: { id: lessonId },
select: { module: { select: { course: { select: { id: true } } } } },
});
if (!lesson) throw new Error("Lesson not found");
const isAdmin = session.user.role === "admin";
if (!isAdmin) {
const enrollment = await prisma.courseEnrollment.findUnique({
where: {
userId_courseId: {
userId: session.user.id,
courseId: lesson.module.course.id,
},
},
});
if (!enrollment) throw new Error("Not enrolled");
if (enrollment.expiresAt && enrollment.expiresAt < new Date()) throw new Error("Access expired");
}
await prisma.lessonComment.create({
data: { lessonId, userId: session.user.id, text: trimmed },
});
revalidatePath(`/courses/${slug}/lessons/${lessonId}`);
}
export async function deleteComment(commentId: string, lessonId: string, slug: string) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) throw new Error("Unauthorized");
const comment = await prisma.lessonComment.findUnique({ where: { id: commentId } });
if (!comment) throw new Error("Not found");
const canDelete =
comment.userId === session.user.id ||
session.user.role === "curator" ||
session.user.role === "admin";
if (!canDelete) throw new Error("Forbidden");
await prisma.lessonComment.update({
where: { id: commentId },
data: { deleted: true },
});
revalidatePath(`/courses/${slug}/lessons/${lessonId}`);
}