Initial commit: Edu Helper (Docker, React, Express, Prisma)
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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"}]`;
|
||||
|
||||
/** Тесты привязаны к текущему пользователю (ученик — свои; наставник — свои черновые, не к ученику). */
|
||||
function testOwnerId(req: AuthRequest): number {
|
||||
return req.user!.id;
|
||||
}
|
||||
|
||||
router.get("/", async (req: AuthRequest, res: Response) => {
|
||||
const ownerId = testOwnerId(req);
|
||||
const tests = await prisma.test.findMany({
|
||||
where: { studentId: ownerId },
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { results: true },
|
||||
});
|
||||
res.json(tests);
|
||||
});
|
||||
|
||||
router.get("/:id", async (req: AuthRequest, res: Response) => {
|
||||
const id = parseInt(req.params.id as string, 10);
|
||||
const ownerId = testOwnerId(req);
|
||||
const test = await prisma.test.findFirst({
|
||||
where: { id, studentId: ownerId },
|
||||
include: { results: true },
|
||||
});
|
||||
|
||||
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 ownerId = testOwnerId(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: ownerId,
|
||||
},
|
||||
});
|
||||
|
||||
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 ownerId = testOwnerId(req);
|
||||
const { answers } = req.body as { answers: Record<string, string> };
|
||||
|
||||
const test = await prisma.test.findFirst({ where: { id, studentId: ownerId } });
|
||||
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,
|
||||
answers: JSON.stringify(answers),
|
||||
score,
|
||||
total,
|
||||
},
|
||||
});
|
||||
|
||||
res.json({ ...result, persisted: true as const });
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user