- Update ai-feedback-dialog, attachment-picker, blocks/text-study-block - Update detail-panel (detail-panel, detail-props) - Update inline-question-editor, lesson-plan-card, lesson-plan-editor, lesson-plan-mobile-view, schedule-dialog, version-history-drawer - Update paper-editor (inline-qa-dialog, paper-context-menu) - Update structure-tree (structure-tree, tree-node-row) - Update config/block-registry, hooks (editor-slice, use-lesson-plan-persistence, use-node-ai-assist), lib (ai-node-assist, consistency-check, export, type-guards) - Update publish-service, services/default-question-service - Update data-access files (ai-evaluation, analytics, attachments, calendar, comments, formative, knowledge, review, schedules, templates, versions, main)
176 lines
5.0 KiB
TypeScript
176 lines
5.0 KiB
TypeScript
/**
|
|
* M6 资源附件库 - 数据访问层
|
|
*/
|
|
import "server-only";
|
|
import { cacheFn } from "@/shared/lib/cache";
|
|
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 getAttachmentsByPlanIdRaw(
|
|
planId: string,
|
|
): Promise<LessonPlanAttachment[]> {
|
|
const rows = await db
|
|
.select({
|
|
id: lessonPlanAttachments.id,
|
|
planId: lessonPlanAttachments.planId,
|
|
blockId: lessonPlanAttachments.blockId,
|
|
fileId: lessonPlanAttachments.fileId,
|
|
displayName: lessonPlanAttachments.displayName,
|
|
attachmentType: lessonPlanAttachments.attachmentType,
|
|
uploadedBy: lessonPlanAttachments.uploadedBy,
|
|
createdAt: lessonPlanAttachments.createdAt,
|
|
})
|
|
.from(lessonPlanAttachments)
|
|
.where(eq(lessonPlanAttachments.planId, planId))
|
|
.orderBy(desc(lessonPlanAttachments.createdAt));
|
|
|
|
return rows.map(mapRowToAttachment);
|
|
}
|
|
|
|
export const getAttachmentsByPlanId = cacheFn(getAttachmentsByPlanIdRaw, { tags: ["lesson-preparation"], ttl: 300, keyParts: ["lp", "attachments", "by-plan"] });
|
|
|
|
/**
|
|
* 查询特定 Block 的附件
|
|
*/
|
|
export async function getAttachmentsByBlockIdRaw(
|
|
planId: string,
|
|
blockId: string,
|
|
): Promise<LessonPlanAttachment[]> {
|
|
const rows = await db
|
|
.select({
|
|
id: lessonPlanAttachments.id,
|
|
planId: lessonPlanAttachments.planId,
|
|
blockId: lessonPlanAttachments.blockId,
|
|
fileId: lessonPlanAttachments.fileId,
|
|
displayName: lessonPlanAttachments.displayName,
|
|
attachmentType: lessonPlanAttachments.attachmentType,
|
|
uploadedBy: lessonPlanAttachments.uploadedBy,
|
|
createdAt: lessonPlanAttachments.createdAt,
|
|
})
|
|
.from(lessonPlanAttachments)
|
|
.where(
|
|
and(
|
|
eq(lessonPlanAttachments.planId, planId),
|
|
eq(lessonPlanAttachments.blockId, blockId),
|
|
),
|
|
)
|
|
.orderBy(desc(lessonPlanAttachments.createdAt));
|
|
|
|
return rows.map(mapRowToAttachment);
|
|
}
|
|
|
|
export const getAttachmentsByBlockId = cacheFn(getAttachmentsByBlockIdRaw, { tags: ["lesson-preparation"], ttl: 300, keyParts: ["lp", "attachments", "by-block"] });
|
|
|
|
/**
|
|
* 添加附件
|
|
*/
|
|
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 getAttachmentByIdRaw(
|
|
id: string,
|
|
): Promise<LessonPlanAttachment | null> {
|
|
const rows = await db
|
|
.select({
|
|
id: lessonPlanAttachments.id,
|
|
planId: lessonPlanAttachments.planId,
|
|
blockId: lessonPlanAttachments.blockId,
|
|
fileId: lessonPlanAttachments.fileId,
|
|
displayName: lessonPlanAttachments.displayName,
|
|
attachmentType: lessonPlanAttachments.attachmentType,
|
|
uploadedBy: lessonPlanAttachments.uploadedBy,
|
|
createdAt: lessonPlanAttachments.createdAt,
|
|
})
|
|
.from(lessonPlanAttachments)
|
|
.where(eq(lessonPlanAttachments.id, id))
|
|
.limit(1);
|
|
return rows.length === 0 ? null : mapRowToAttachment(rows[0]!);
|
|
}
|
|
|
|
export const getAttachmentById = cacheFn(getAttachmentByIdRaw, { tags: ["lesson-preparation"], ttl: 600, keyParts: ["lp", "attachments", "by-id"] });
|
|
|
|
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,
|
|
};
|
|
}
|