Stage 1: Course/Module/Lesson CRUD admin UI with TipTap editor

This commit is contained in:
2026-04-07 11:36:27 +05:00
parent 9d82b73e58
commit d356dddc96
30 changed files with 2090 additions and 41 deletions
+62
View File
@@ -0,0 +1,62 @@
"use server";
import { prisma } from "@/lib/prisma";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
async function requireAdmin() {
const session = await auth.api.getSession({ headers: await headers() });
if (!session || session.user.role !== "admin") throw new Error("Forbidden");
}
function slugify(str: string): string {
const map: Record<string, string> = {
а:"a",б:"b",в:"v",г:"g",д:"d",е:"e",ё:"yo",ж:"zh",з:"z",и:"i",й:"y",
к:"k",л:"l",м:"m",н:"n",о:"o",п:"p",р:"r",с:"s",т:"t",у:"u",ф:"f",
х:"kh",ц:"ts",ч:"ch",ш:"sh",щ:"shch",ъ:"",ы:"y",ь:"",э:"e",ю:"yu",я:"ya",
};
return str.toLowerCase()
.replace(/[а-яё]/g, (c) => map[c] ?? c)
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
export async function createCourse(formData: FormData) {
await requireAdmin();
const title = formData.get("title") as string;
const slug = (formData.get("slug") as string).trim() || slugify(title);
const description = (formData.get("description") as string) || null;
const course = await prisma.course.create({
data: { title, slug, description },
});
revalidatePath("/admin/courses");
redirect(`/admin/courses/${course.id}`);
}
export async function updateCourse(courseId: string, formData: FormData) {
await requireAdmin();
const title = formData.get("title") as string;
const slug = formData.get("slug") as string;
const description = (formData.get("description") as string) || null;
const published = formData.get("published") === "true";
const coverImage = (formData.get("coverImage") as string) || null;
await prisma.course.update({
where: { id: courseId },
data: { title, slug, description, published, coverImage },
});
revalidatePath("/admin/courses");
revalidatePath(`/admin/courses/${courseId}`);
}
export async function deleteCourse(courseId: string) {
await requireAdmin();
await prisma.course.delete({ where: { id: courseId } });
revalidatePath("/admin/courses");
redirect("/admin/courses");
}