import "server-only"; import { and, desc, eq, inArray, max } from "drizzle-orm"; import { createId } from "@paralleldrive/cuid2"; import { db } from "@/shared/db"; import { lessonPlanVersions, lessonPlans } from "@/shared/db/schema"; import { normalizeDocument } from "./data-access"; import type { LessonPlanDocument, LessonPlanVersion } from "./types"; export async function getLessonPlanVersions( planId: string, userId: string, ): Promise { // 校验归属 const plan = await db .select({ id: lessonPlans.id }) .from(lessonPlans) .where( and(eq(lessonPlans.id, planId), eq(lessonPlans.creatorId, userId)), ) .limit(1); if (plan.length === 0) return []; const rows = await db .select() .from(lessonPlanVersions) .where(eq(lessonPlanVersions.planId, planId)) .orderBy(desc(lessonPlanVersions.versionNo)); return rows as unknown as LessonPlanVersion[]; } export async function createLessonPlanVersion(input: { planId: string; content: LessonPlanDocument; userId: string; isAuto: boolean; label?: string; }): Promise<{ versionNo: number }> { // 取当前最大 versionNo const maxRow = await db .select({ maxNo: max(lessonPlanVersions.versionNo) }) .from(lessonPlanVersions) .where(eq(lessonPlanVersions.planId, input.planId)); const nextNo = (maxRow[0]?.maxNo ?? 0) + 1; await db.insert(lessonPlanVersions).values({ id: createId(), planId: input.planId, versionNo: nextNo, label: input.label ?? null, content: input.content, isAuto: input.isAuto, creatorId: input.userId, }); return { versionNo: nextNo }; } export async function getVersionContent( planId: string, versionNo: number, userId: string, ): Promise { // 校验归属 const plan = await db .select({ id: lessonPlans.id }) .from(lessonPlans) .where( and(eq(lessonPlans.id, planId), eq(lessonPlans.creatorId, userId)), ) .limit(1); if (plan.length === 0) return null; const rows = await db .select({ content: lessonPlanVersions.content }) .from(lessonPlanVersions) .where( and( eq(lessonPlanVersions.planId, planId), eq(lessonPlanVersions.versionNo, versionNo), ), ) .limit(1); if (rows.length === 0) return null; return normalizeDocument(rows[0].content); } export async function revertToVersion( planId: string, versionNo: number, userId: string, ): Promise<{ newVersionNo: number } | null> { const content = await getVersionContent(planId, versionNo, userId); if (!content) return null; // 用该版本 content 覆盖当前 + 生成新版本 await db .update(lessonPlans) .set({ content, lastSavedAt: new Date() }) .where( and(eq(lessonPlans.id, planId), eq(lessonPlans.creatorId, userId)), ); const { versionNo: newNo } = await createLessonPlanVersion({ planId, content, userId, isAuto: false, label: `回退到 v${versionNo}`, }); return { newVersionNo: newNo }; } export async function pruneAutoVersions( planId: string, keep = 50, ): Promise { const rows = await db .select({ id: lessonPlanVersions.id, isAuto: lessonPlanVersions.isAuto, versionNo: lessonPlanVersions.versionNo, }) .from(lessonPlanVersions) .where(eq(lessonPlanVersions.planId, planId)) .orderBy(desc(lessonPlanVersions.versionNo)); if (rows.length <= keep) return; // 保留前 keep 条;超出部分只删 isAuto=true 的 const toDelete = rows.slice(keep).filter((r) => r.isAuto); if (toDelete.length === 0) return; await db .delete(lessonPlanVersions) .where( and( eq(lessonPlanVersions.planId, planId), inArray( lessonPlanVersions.id, toDelete.map((r) => r.id), ), ), ); }