refactor: P1-2 actions 层 DB 操作下沉到 data-access (exams/homework/questions/announcements)
This commit is contained in:
@@ -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,
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user