refactor: P1-2 actions 层 DB 操作下沉到 data-access (exams/homework/questions/announcements)

This commit is contained in:
SpecialX
2026-06-18 02:31:16 +08:00
parent 2c8e229e00
commit 84d6636bd1
9 changed files with 858 additions and 438 deletions

View File

@@ -1,10 +1,21 @@
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 { chapters, knowledgePoints, questions, questionsToKnowledgePoints, textbooks } from "@/shared/db/schema";
import { and, asc, count, desc, eq, inArray, sql, type SQL } from "drizzle-orm";
import { cache } from "react";
import type { Question, QuestionType } from "./types";
import { createId } from "@paralleldrive/cuid2";
import type { CreateQuestionInput } from "./schema";
import type { KnowledgePointOption, Question, QuestionType } from "./types";
type Tx = Parameters<Parameters<typeof db.transaction>[0]>[0]
export type UpdateQuestionInput = {
type: QuestionType
difficulty: number
content: unknown
knowledgePointIds?: string[]
}
export type GetQuestionsParams = {
q?: string;
@@ -136,3 +147,153 @@ export const getQuestionsDashboardStats = cache(async (): Promise<QuestionsDashb
const [row] = await db.select({ value: count() }).from(questions)
return { questionCount: Number(row?.value ?? 0) }
})
async function insertQuestionWithRelations(
tx: Tx,
input: CreateQuestionInput,
authorId: string,
parentId: string | null = null
): Promise<string> {
const newQuestionId = createId();
await tx.insert(questions).values({
id: newQuestionId,
content: input.content,
type: input.type,
difficulty: input.difficulty,
authorId: authorId,
parentId: parentId,
});
if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
await tx.insert(questionsToKnowledgePoints).values(
input.knowledgePointIds.map((kpId) => ({
questionId: newQuestionId,
knowledgePointId: kpId,
}))
);
}
if (input.subQuestions && input.subQuestions.length > 0) {
for (const subQ of input.subQuestions) {
await insertQuestionWithRelations(tx, subQ, authorId, newQuestionId);
}
}
return newQuestionId;
}
export async function createQuestionWithRelations(
input: CreateQuestionInput,
authorId: string
): Promise<string> {
return await db.transaction(async (tx) => {
return await insertQuestionWithRelations(tx, input, authorId, null);
});
}
export async function updateQuestionById(
id: string,
input: UpdateQuestionInput,
canEditAll: boolean,
authorId: string
): Promise<void> {
await db.transaction(async (tx) => {
await tx
.update(questions)
.set({
type: input.type,
difficulty: input.difficulty,
content: input.content,
updatedAt: new Date(),
})
.where(
canEditAll
? eq(questions.id, id)
: and(eq(questions.id, id), eq(questions.authorId, authorId))
);
await tx
.delete(questionsToKnowledgePoints)
.where(eq(questionsToKnowledgePoints.questionId, id));
if (input.knowledgePointIds && input.knowledgePointIds.length > 0) {
await tx.insert(questionsToKnowledgePoints).values(
input.knowledgePointIds.map((kpId) => ({
questionId: id,
knowledgePointId: kpId,
}))
);
}
});
}
async function deleteQuestionRecursive(tx: Tx, questionId: string): Promise<void> {
const children = await tx
.select({ id: questions.id })
.from(questions)
.where(eq(questions.parentId, questionId));
for (const child of children) {
await deleteQuestionRecursive(tx, child.id);
}
await tx.delete(questions).where(eq(questions.id, questionId));
}
export async function deleteQuestionByIdRecursive(
questionId: string,
canDeleteAll: boolean,
authorId: string
): Promise<void> {
await db.transaction(async (tx) => {
const q = await tx.query.questions.findFirst({
where: eq(questions.id, questionId),
});
if (!q) {
throw new Error("Question not found");
}
if (!canDeleteAll && q.authorId !== authorId) {
throw new Error("Unauthorized");
}
await deleteQuestionRecursive(tx, questionId);
});
}
export async function getKnowledgePointOptions(): Promise<KnowledgePointOption[]> {
const rows = await db
.select({
id: knowledgePoints.id,
name: knowledgePoints.name,
chapterId: chapters.id,
chapterTitle: chapters.title,
textbookId: textbooks.id,
textbookTitle: textbooks.title,
subject: textbooks.subject,
grade: textbooks.grade,
})
.from(knowledgePoints)
.leftJoin(chapters, eq(chapters.id, knowledgePoints.chapterId))
.leftJoin(textbooks, eq(textbooks.id, chapters.textbookId))
.orderBy(
asc(textbooks.title),
asc(chapters.order),
asc(chapters.title),
asc(knowledgePoints.order),
asc(knowledgePoints.name)
);
return rows.map((row) => ({
id: row.id,
name: row.name,
chapterId: row.chapterId ?? null,
chapterTitle: row.chapterTitle ?? null,
textbookId: row.textbookId ?? null,
textbookTitle: row.textbookTitle ?? null,
subject: row.subject ?? null,
grade: row.grade ?? null,
}));
}