Files
NextEdu/src/modules/lesson-preparation/data-access-attachments.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

142 lines
3.3 KiB
TypeScript

/**
* 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,
};
}