Phase 6 complete
This commit is contained in:
parent
93ea01525e
commit
3a22b42492
11 changed files with 735 additions and 103 deletions
|
|
@ -11,7 +11,20 @@ app.use(cors({
|
|||
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
||||
credentials: true,
|
||||
}));
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
express.json({ limit: '10mb' })(req, res, (err) => {
|
||||
if (err instanceof SyntaxError && 'body' in err) {
|
||||
res.status(400).json({ error: 'Invalid JSON' });
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
next(err);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/health', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
|
|
|
|||
|
|
@ -84,12 +84,45 @@ router.get('/:id', (req: AuthenticatedRequest, res: Response) => {
|
|||
});
|
||||
});
|
||||
|
||||
function validateQuizBody(body: QuizBody): string | null {
|
||||
const { title, source, questions } = body;
|
||||
|
||||
if (!title?.trim()) {
|
||||
return 'Title is required and cannot be empty';
|
||||
}
|
||||
|
||||
if (!source || !['manual', 'ai_generated'].includes(source)) {
|
||||
return 'Source must be "manual" or "ai_generated"';
|
||||
}
|
||||
|
||||
if (!questions || !Array.isArray(questions) || questions.length === 0) {
|
||||
return 'At least one question is required';
|
||||
}
|
||||
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
const q = questions[i];
|
||||
if (!q.text?.trim()) {
|
||||
return `Question ${i + 1} text is required`;
|
||||
}
|
||||
if (!q.options || !Array.isArray(q.options) || q.options.length < 2) {
|
||||
return `Question ${i + 1} must have at least 2 options`;
|
||||
}
|
||||
const hasCorrect = q.options.some(o => o.isCorrect);
|
||||
if (!hasCorrect) {
|
||||
return `Question ${i + 1} must have at least one correct answer`;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
router.post('/', (req: AuthenticatedRequest, res: Response) => {
|
||||
const body = req.body as QuizBody;
|
||||
const { title, source, aiTopic, questions } = body;
|
||||
|
||||
if (!title?.trim() || !source || !questions?.length) {
|
||||
res.status(400).json({ error: 'Missing required fields: title, source, questions' });
|
||||
const validationError = validateQuizBody(body);
|
||||
if (validationError) {
|
||||
res.status(400).json({ error: validationError });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +190,33 @@ router.put('/:id', (req: AuthenticatedRequest, res: Response) => {
|
|||
const { title, questions } = body;
|
||||
const quizId = req.params.id;
|
||||
|
||||
if (!title?.trim()) {
|
||||
res.status(400).json({ error: 'Title is required and cannot be empty' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!questions || !Array.isArray(questions) || questions.length === 0) {
|
||||
res.status(400).json({ error: 'At least one question is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < questions.length; i++) {
|
||||
const q = questions[i];
|
||||
if (!q.text?.trim()) {
|
||||
res.status(400).json({ error: `Question ${i + 1} text is required` });
|
||||
return;
|
||||
}
|
||||
if (!q.options || !Array.isArray(q.options) || q.options.length < 2) {
|
||||
res.status(400).json({ error: `Question ${i + 1} must have at least 2 options` });
|
||||
return;
|
||||
}
|
||||
const hasCorrect = q.options.some(o => o.isCorrect);
|
||||
if (!hasCorrect) {
|
||||
res.status(400).json({ error: `Question ${i + 1} must have at least one correct answer` });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const existing = db.prepare(`
|
||||
SELECT id FROM quizzes WHERE id = ? AND user_id = ?
|
||||
`).get(quizId, req.user!.sub);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue