Files
NextEdu/src/modules/lesson-preparation/data-access-templates.ts
SpecialX 2197e68069 feat(lesson-preparation): add anchor canvas design, new blocks, and textbook content node
- Add anchor injector for canvas-based anchor positioning

- Add new block components: blackboard, homework, import, key-point, new-teaching, objective, summary

- Add textbook content node for React Flow canvas

- Update actions (kp, publish, main), data-access (templates, versions, main)

- Update editor, node-editor, block-renderer, and picker components

- Update schema, types, hooks, and lib utilities (document-migration, node-summary, rf-mappers)
2026-06-23 17:37:19 +08:00

130 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "server-only";
import { and, eq } from "drizzle-orm";
import { createId } from "@paralleldrive/cuid2";
import { db } from "@/shared/db";
import { lessonPlanTemplates, lessonPlans } from "@/shared/db/schema";
import { SYSTEM_TEMPLATES } from "./constants";
import { normalizeDocument, LessonPlanDataError } from "./data-access";
import type {
LessonPlanTemplate,
TemplateBlockSkeleton,
TemplateType,
TemplateScope,
} from "./types";
// ---- 类型守卫:安全地将 DB string 收窄为联合类型 ----
const TEMPLATE_TYPES = ["system", "personal"] as const;
function isTemplateType(v: string): v is TemplateType {
return (TEMPLATE_TYPES as readonly string[]).includes(v);
}
const TEMPLATE_SCOPES = ["regular", "review", "experiment", "inquiry", "blank", "custom"] as const;
function isTemplateScope(v: string): v is TemplateScope {
return (TEMPLATE_SCOPES as readonly string[]).includes(v);
}
// ---- 类型映射Drizzle 行 → LessonPlanTemplateDate → ISO string----
function mapRowToTemplate(row: {
id: string;
name: string;
type: string;
scope: string;
blocks: unknown;
creatorId: string | null;
createdAt: Date;
updatedAt: Date;
}): LessonPlanTemplate {
return {
id: row.id,
name: row.name,
type: isTemplateType(row.type) ? row.type : "personal",
scope: isTemplateScope(row.scope) ? row.scope : "custom",
// 从 unknown 转换为 TemplateBlockSkeleton[]DB JSON 字段)
blocks: row.blocks as LessonPlanTemplate["blocks"],
creatorId: row.creatorId,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
export async function getLessonPlanTemplates(
userId: string,
): Promise<LessonPlanTemplate[]> {
// system 模板(内存)+ personal 模板DB
const systemTemplates: LessonPlanTemplate[] = SYSTEM_TEMPLATES.map((t) => ({
id: t.id,
name: t.name,
type: "system",
scope: t.scope,
blocks: t.blocks,
creatorId: null,
createdAt: "",
updatedAt: "",
}));
const personalRows = await db
.select()
.from(lessonPlanTemplates)
.where(
and(
eq(lessonPlanTemplates.type, "personal"),
eq(lessonPlanTemplates.creatorId, userId),
),
);
const personalTemplates = personalRows.map(mapRowToTemplate);
return [...systemTemplates, ...personalTemplates];
}
export async function saveAsTemplate(input: {
sourcePlanId: string;
name: string;
userId: string;
}): Promise<{ templateId: string }> {
// 从课案 content 提取 block 骨架
const plan = await db
.select({ content: lessonPlans.content })
.from(lessonPlans)
.where(
and(
eq(lessonPlans.id, input.sourcePlanId),
eq(lessonPlans.creatorId, input.userId),
),
)
.limit(1);
if (plan.length === 0) throw new LessonPlanDataError("NOT_FOUND");
const doc = normalizeDocument(plan[0].content);
const skeleton: TemplateBlockSkeleton[] = doc.nodes
.filter((b): b is import("./types").LessonPlanNode => b.type !== "textbook_content")
.map((b) => ({
type: b.type,
title: b.title,
}));
const templateId = createId();
await db.insert(lessonPlanTemplates).values({
id: templateId,
name: input.name,
type: "personal",
scope: "custom",
blocks: skeleton,
creatorId: input.userId,
});
return { templateId };
}
export async function deletePersonalTemplate(
templateId: string,
userId: string,
): Promise<void> {
await db.delete(lessonPlanTemplates).where(
and(
eq(lessonPlanTemplates.id, templateId),
eq(lessonPlanTemplates.type, "personal"),
eq(lessonPlanTemplates.creatorId, userId),
),
);
}