Files
NextEdu/src/modules/lesson-preparation/data-access-templates.ts
SpecialX 20023e13fd feat(lesson-preparation): add AI evaluation, analytics, attachments, calendar, comments, review, substitutes, formative, and version diff
- Add actions-ai-evaluation, actions-analytics, actions-attachments, actions-calendar, actions-comments, actions-formative, actions-questions, actions-review, actions-substitutes

- Add corresponding data-access layers for each new action module

- Add calendar-view, curriculum-map-view, version-diff-viewer components

- Add editor-slice, selection-slice, version-slice hooks for state management

- Add document-diff and scope-check lib utilities

- Add default-question-service and external-questions-bridge services
2026-07-03 10:25:21 +08:00

123 lines
3.2 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 {
normalizeTemplateBlocks,
isTemplateType,
isTemplateScope,
} from "./lib/type-guards";
import type {
LessonPlanTemplate,
TemplateBlockSkeleton,
} from "./types";
// ---- 类型映射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",
// P1 修复:使用 normalizeTemplateBlocks 安全转换 DB JSON 字段,替代 as 断言
blocks: normalizeTemplateBlocks(row.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),
),
);
}