Files
CICD/src/modules/questions/data-access.ts
SpecialX f8e39f518d feat(teacher): 题库模块(QuestionBank)
工作内容

- 新增 /teacher/questions 页面,支持 Suspense/空状态/加载态

- 题库 CRUD Server Actions:创建/更新/递归删除子题,变更后 revalidatePath

- getQuestions 支持 q/type/difficulty/knowledgePointId 筛选与分页返回 meta

- UI:表格列/筛选器/创建编辑弹窗,content JSON 兼容组卷

- 更新中文设计文档:docs/design/004_question_bank_implementation.md
2025-12-30 19:04:22 +08:00

130 lines
3.0 KiB
TypeScript

import 'server-only';
import { db } from "@/shared/db";
import { questions, questionsToKnowledgePoints } from "@/shared/db/schema";
import { and, count, desc, eq, inArray, sql, type SQL } from "drizzle-orm";
import { cache } from "react";
import type { Question, QuestionType } from "./types";
export type GetQuestionsParams = {
q?: string;
page?: number;
pageSize?: number;
ids?: string[];
knowledgePointId?: string;
type?: QuestionType;
difficulty?: number;
};
export const getQuestions = cache(async ({
q,
page = 1,
pageSize = 50,
ids,
knowledgePointId,
type,
difficulty,
}: GetQuestionsParams = {}) => {
const offset = (page - 1) * pageSize;
const conditions: SQL[] = [];
if (ids && ids.length > 0) {
conditions.push(inArray(questions.id, ids));
}
if (q && q.trim().length > 0) {
const needle = `%${q.trim().toLowerCase()}%`;
conditions.push(
sql`LOWER(CAST(${questions.content} AS CHAR)) LIKE ${needle}`
);
}
if (type) {
conditions.push(eq(questions.type, type));
}
if (difficulty) {
conditions.push(eq(questions.difficulty, difficulty));
}
if (knowledgePointId) {
const subQuery = db
.select({ questionId: questionsToKnowledgePoints.questionId })
.from(questionsToKnowledgePoints)
.where(eq(questionsToKnowledgePoints.knowledgePointId, knowledgePointId));
conditions.push(inArray(questions.id, subQuery));
}
if (!ids || ids.length === 0) {
conditions.push(sql`${questions.parentId} IS NULL`)
}
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
const [totalResult] = await db
.select({ count: count() })
.from(questions)
.where(whereClause);
const total = Number(totalResult?.count ?? 0);
const rows = await db.query.questions.findMany({
where: whereClause,
limit: pageSize,
offset: offset,
orderBy: [desc(questions.createdAt)],
with: {
questionsToKnowledgePoints: {
with: {
knowledgePoint: true,
},
},
author: {
columns: {
id: true,
name: true,
image: true,
},
},
children: true,
},
});
return {
data: rows.map((row) => {
const knowledgePoints =
row.questionsToKnowledgePoints?.map((rel) => rel.knowledgePoint) ?? [];
const author = row.author
? {
id: row.author.id,
name: row.author.name,
image: row.author.image,
}
: null;
const mapped: Question = {
id: row.id,
content: row.content,
type: row.type,
difficulty: row.difficulty ?? 1,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
author,
knowledgePoints: knowledgePoints.map((kp) => ({ id: kp.id, name: kp.name })),
childrenCount: row.children?.length ?? 0,
};
return mapped;
}),
meta: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
},
};
});