768a38b9d3
- CourseTree: expandable module/lesson overview with Eye/Video icons - SortableLessons: Kinescope ID in create form, published toggle, move-to-module dropdown - Actions: toggleLessonPublished, moveLessonToModule, updateModule with description - Schema: add description field to Module model + migration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
"use server";
|
|
|
|
import { prisma } from "@/lib/prisma";
|
|
import { headers } from "next/headers";
|
|
import { auth } from "@/lib/auth";
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
|
|
async function requireAdmin() {
|
|
const session = await auth.api.getSession({ headers: await headers() });
|
|
if (!session || session.user.role !== "admin") throw new Error("Forbidden");
|
|
}
|
|
|
|
export async function createLesson(moduleId: string, courseId: string, formData: FormData) {
|
|
await requireAdmin();
|
|
const title = formData.get("title") as string;
|
|
const kinescopeId = (formData.get("kinescopeId") as string | null)?.trim() || null;
|
|
const count = await prisma.lesson.count({ where: { moduleId } });
|
|
const lesson = await prisma.lesson.create({
|
|
data: { moduleId, title, kinescopeId, order: count },
|
|
});
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
|
|
redirect(`/admin/courses/${courseId}/modules/${moduleId}/lessons/${lesson.id}`);
|
|
}
|
|
|
|
export async function updateLesson(lessonId: string, courseId: string, moduleId: string, formData: FormData) {
|
|
await requireAdmin();
|
|
const title = formData.get("title") as string;
|
|
await prisma.lesson.update({ where: { id: lessonId }, data: { title } });
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
|
|
}
|
|
|
|
export async function deleteLesson(lessonId: string, courseId: string, moduleId: string) {
|
|
await requireAdmin();
|
|
await prisma.lesson.delete({ where: { id: lessonId } });
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
|
|
}
|
|
|
|
export async function reorderLessons(moduleId: string, courseId: string, orderedIds: string[]) {
|
|
await requireAdmin();
|
|
await Promise.all(
|
|
orderedIds.map((id, index) =>
|
|
prisma.lesson.update({ where: { id }, data: { order: index } })
|
|
)
|
|
);
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
|
|
}
|
|
|
|
export async function toggleLessonPublished(
|
|
lessonId: string,
|
|
courseId: string,
|
|
moduleId: string,
|
|
currentValue: boolean
|
|
) {
|
|
await requireAdmin();
|
|
await prisma.lesson.update({
|
|
where: { id: lessonId },
|
|
data: { published: !currentValue },
|
|
});
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
|
|
revalidatePath(`/admin/courses/${courseId}`);
|
|
}
|
|
|
|
export async function moveLessonToModule(
|
|
lessonId: string,
|
|
targetModuleId: string,
|
|
courseId: string,
|
|
sourceModuleId: string
|
|
) {
|
|
await requireAdmin();
|
|
// verify target module belongs to same course
|
|
const target = await prisma.module.findFirst({
|
|
where: { id: targetModuleId, courseId },
|
|
});
|
|
if (!target) throw new Error("Module not found");
|
|
|
|
const maxOrder = await prisma.lesson.aggregate({
|
|
where: { moduleId: targetModuleId },
|
|
_max: { order: true },
|
|
});
|
|
|
|
await prisma.lesson.update({
|
|
where: { id: lessonId },
|
|
data: { moduleId: targetModuleId, order: (maxOrder._max.order ?? -1) + 1 },
|
|
});
|
|
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${sourceModuleId}`);
|
|
revalidatePath(`/admin/courses/${courseId}/modules/${targetModuleId}`);
|
|
revalidatePath(`/admin/courses/${courseId}`);
|
|
}
|