35f27a6eb7
Made-with: Cursor
163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
import { Router, Response } from "express";
|
||
import prisma from "../lib/prisma";
|
||
import { getDeepSeekClient, getPrompt } from "../lib/deepseek";
|
||
import type { AuthRequest } from "../middleware/auth";
|
||
|
||
const router = Router();
|
||
|
||
const DEFAULT_TEST_PROMPT = `Ты — составитель тестов. Сгенерируй тест из РОВНО 10 вопросов.
|
||
|
||
Требования к вопросам:
|
||
- Вопросы должны проверять ПОНИМАНИЕ темы, а не запоминание фактов
|
||
- Сложность: 3 лёгких, 4 средних, 3 сложных
|
||
- Каждый вопрос имеет 4 варианта ответа (a, b, c, d) и ровно один правильный
|
||
- Неправильные варианты должны быть правдоподобными (не абсурдными)
|
||
- Вопросы на русском языке
|
||
|
||
ВАЖНО: Верни ТОЛЬКО JSON-массив, без markdown-обёрток (\`\`\`json), без пояснений, ЧИСТЫЙ JSON:
|
||
[{"question":"текст вопроса","options":{"a":"вариант А","b":"вариант Б","c":"вариант В","d":"вариант Г"},"correct":"a"}]`;
|
||
|
||
/** Контекст пары: id назначенного ученика (общие тесты для наставника и ученика). */
|
||
function pairStudentScopeId(req: AuthRequest): number {
|
||
return req.studentId!;
|
||
}
|
||
|
||
const authorSelect = { username: true, displayName: true } as const;
|
||
|
||
router.get("/", async (req: AuthRequest, res: Response) => {
|
||
const scopeId = pairStudentScopeId(req);
|
||
const tests = await prisma.test.findMany({
|
||
where: { studentId: scopeId },
|
||
orderBy: { createdAt: "desc" },
|
||
include: {
|
||
author: { select: authorSelect },
|
||
results: {
|
||
where: { studentId: scopeId },
|
||
orderBy: { createdAt: "asc" },
|
||
},
|
||
},
|
||
});
|
||
res.json(tests);
|
||
});
|
||
|
||
router.get("/:id", async (req: AuthRequest, res: Response) => {
|
||
const id = parseInt(req.params.id as string, 10);
|
||
const scopeId = pairStudentScopeId(req);
|
||
const test = await prisma.test.findFirst({
|
||
where: { id, studentId: scopeId },
|
||
include: {
|
||
author: { select: authorSelect },
|
||
results: {
|
||
where: { studentId: scopeId },
|
||
orderBy: { createdAt: "asc" },
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!test) {
|
||
res.status(404).json({ error: "Test not found" });
|
||
return;
|
||
}
|
||
|
||
res.json(test);
|
||
});
|
||
|
||
router.post("/generate", async (req: AuthRequest, res: Response) => {
|
||
const { topic, fromQuestions } = req.body;
|
||
const scopeId = pairStudentScopeId(req);
|
||
const questionBankStudentId = req.studentId!;
|
||
|
||
const client = await getDeepSeekClient();
|
||
const systemPrompt = await getPrompt("prompt_test", DEFAULT_TEST_PROMPT);
|
||
|
||
let userMessage: string;
|
||
|
||
if (fromQuestions) {
|
||
const questions = await prisma.question.findMany({
|
||
where: { studentId: questionBankStudentId },
|
||
orderBy: { createdAt: "desc" },
|
||
take: 20,
|
||
});
|
||
const questionTexts = questions.map((q: { text: string }) => q.text).join("\n");
|
||
userMessage = `Составь тест на основе этих вопросов, которые задавал пользователь ранее:\n${questionTexts}`;
|
||
} else {
|
||
if (!topic) {
|
||
res.status(400).json({ error: "Topic is required" });
|
||
return;
|
||
}
|
||
userMessage = `Тема: ${topic}`;
|
||
}
|
||
|
||
try {
|
||
const response = await client.chat.completions.create({
|
||
model: "deepseek-chat",
|
||
messages: [
|
||
{ role: "system", content: systemPrompt },
|
||
{ role: "user", content: userMessage },
|
||
],
|
||
});
|
||
|
||
const raw = response.choices[0]?.message?.content || "[]";
|
||
const jsonMatch = raw.match(/\[[\s\S]*\]/);
|
||
const questionsJson = jsonMatch ? jsonMatch[0] : "[]";
|
||
|
||
const test = await prisma.test.create({
|
||
data: {
|
||
topic: topic || "По прошлым вопросам",
|
||
questions: questionsJson,
|
||
studentId: scopeId,
|
||
authorId: req.user!.id,
|
||
},
|
||
include: {
|
||
author: { select: authorSelect },
|
||
results: true,
|
||
},
|
||
});
|
||
|
||
res.json(test);
|
||
} catch (err: any) {
|
||
res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
router.post("/:id/submit", async (req: AuthRequest, res: Response) => {
|
||
const id = parseInt(req.params.id as string, 10);
|
||
const scopeId = pairStudentScopeId(req);
|
||
const { answers } = req.body as { answers: Record<string, string> };
|
||
|
||
const test = await prisma.test.findFirst({ where: { id, studentId: scopeId } });
|
||
if (!test) {
|
||
res.status(404).json({ error: "Test not found" });
|
||
return;
|
||
}
|
||
|
||
const questions = JSON.parse(test.questions);
|
||
let score = 0;
|
||
const total = questions.length;
|
||
|
||
for (const q of questions as { question: string; correct: string }[]) {
|
||
if (answers[q.question] === q.correct) {
|
||
score++;
|
||
}
|
||
}
|
||
|
||
if (req.user!.role === "TUTOR") {
|
||
res.json({ score, total, persisted: false as const });
|
||
return;
|
||
}
|
||
|
||
const result = await prisma.testResult.create({
|
||
data: {
|
||
testId: id,
|
||
studentId: req.user!.id,
|
||
answers: JSON.stringify(answers),
|
||
score,
|
||
total,
|
||
},
|
||
});
|
||
|
||
res.json({ ...result, persisted: true as const });
|
||
});
|
||
|
||
export default router;
|