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
This commit is contained in:
141
src/modules/lesson-preparation/data-access-attachments.ts
Normal file
141
src/modules/lesson-preparation/data-access-attachments.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* M6 资源附件库 - 数据访问层
|
||||
*/
|
||||
import "server-only";
|
||||
import { db } from "@/shared/db";
|
||||
import { lessonPlanAttachments } from "@/shared/db/schema";
|
||||
import { and, eq, desc } from "drizzle-orm";
|
||||
import type { AttachmentType } from "./lib/type-guards";
|
||||
import { isAttachmentType } from "./lib/type-guards";
|
||||
|
||||
/** 附件记录 */
|
||||
export interface LessonPlanAttachment {
|
||||
id: string;
|
||||
planId: string;
|
||||
blockId?: string;
|
||||
fileId: string;
|
||||
displayName: string;
|
||||
attachmentType: AttachmentType;
|
||||
uploadedBy: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询课案的所有附件
|
||||
*/
|
||||
export async function getAttachmentsByPlanId(
|
||||
planId: string,
|
||||
): Promise<LessonPlanAttachment[]> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(lessonPlanAttachments)
|
||||
.where(eq(lessonPlanAttachments.planId, planId))
|
||||
.orderBy(desc(lessonPlanAttachments.createdAt));
|
||||
|
||||
return rows.map(mapRowToAttachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询特定 Block 的附件
|
||||
*/
|
||||
export async function getAttachmentsByBlockId(
|
||||
planId: string,
|
||||
blockId: string,
|
||||
): Promise<LessonPlanAttachment[]> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(lessonPlanAttachments)
|
||||
.where(
|
||||
and(
|
||||
eq(lessonPlanAttachments.planId, planId),
|
||||
eq(lessonPlanAttachments.blockId, blockId),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(lessonPlanAttachments.createdAt));
|
||||
|
||||
return rows.map(mapRowToAttachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加附件
|
||||
*/
|
||||
export async function createAttachment(
|
||||
input: {
|
||||
planId: string;
|
||||
blockId?: string;
|
||||
fileId: string;
|
||||
displayName: string;
|
||||
attachmentType?: AttachmentType;
|
||||
},
|
||||
uploadedBy: string,
|
||||
): Promise<LessonPlanAttachment> {
|
||||
const [row] = await db.insert(lessonPlanAttachments).values({
|
||||
planId: input.planId,
|
||||
blockId: input.blockId,
|
||||
fileId: input.fileId,
|
||||
displayName: input.displayName,
|
||||
attachmentType: input.attachmentType ?? "reference",
|
||||
uploadedBy,
|
||||
});
|
||||
const insertedId = row.insertId;
|
||||
const all = await getAttachmentsByPlanId(input.planId);
|
||||
const created = all.find((a) => a.id === String(insertedId));
|
||||
if (!created) throw new Error("ATTACHMENT_CREATE_FAILED");
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除附件
|
||||
*/
|
||||
export async function deleteAttachment(
|
||||
attachmentId: string,
|
||||
): Promise<void> {
|
||||
await db
|
||||
.delete(lessonPlanAttachments)
|
||||
.where(eq(lessonPlanAttachments.id, attachmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新附件类型
|
||||
*/
|
||||
export async function updateAttachmentType(
|
||||
attachmentId: string,
|
||||
attachmentType: AttachmentType,
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(lessonPlanAttachments)
|
||||
.set({ attachmentType })
|
||||
.where(eq(lessonPlanAttachments.id, attachmentId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 获取附件
|
||||
*/
|
||||
export async function getAttachmentById(
|
||||
id: string,
|
||||
): Promise<LessonPlanAttachment | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(lessonPlanAttachments)
|
||||
.where(eq(lessonPlanAttachments.id, id))
|
||||
.limit(1);
|
||||
return rows.length === 0 ? null : mapRowToAttachment(rows[0]!);
|
||||
}
|
||||
|
||||
function mapRowToAttachment(
|
||||
row: typeof lessonPlanAttachments.$inferSelect,
|
||||
): LessonPlanAttachment {
|
||||
const attachmentType = isAttachmentType(row.attachmentType)
|
||||
? row.attachmentType
|
||||
: "reference";
|
||||
return {
|
||||
id: row.id,
|
||||
planId: row.planId,
|
||||
blockId: row.blockId ?? undefined,
|
||||
fileId: row.fileId,
|
||||
displayName: row.displayName,
|
||||
attachmentType,
|
||||
uploadedBy: row.uploadedBy,
|
||||
createdAt: row.createdAt,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user