import { FastifyInstance } from 'fastify'; import { z } from 'zod'; import * as questionService from '../../services/admin/question-service.js'; const createQuestionSchema = z.object({ stem: z.record(z.unknown()), contentType: z.enum(['text', 'image', 'video', 'audio']), correctAnswer: z.string().min(1), distractors: z.array(z.string()).min(2), categoryId: z.string().min(1), difficulty: z.number().min(1).max(5).optional(), knowledgeCard: z.object({ summary: z.string().min(1), deepDive: z.string().optional(), sourceRef: z.string().optional(), }).optional(), }); const updateQuestionSchema = z.object({ stem: z.record(z.unknown()).optional(), contentType: z.enum(['text', 'image', 'video', 'audio']).optional(), correctAnswer: z.string().min(1).optional(), distractors: z.array(z.string()).min(2).optional(), categoryId: z.string().min(1).optional(), difficulty: z.number().min(1).max(5).optional(), status: z.enum(['draft', 'reviewing', 'published', 'archived']).optional(), }); const batchIdsSchema = z.object({ ids: z.array(z.string().uuid()).min(1).max(200) }); export async function adminQuestionsRoutes(app: FastifyInstance): Promise { app.get('/', async (request) => { const { page = '1', limit = '20', status, categoryId } = request.query as Record; const result = await questionService.listQuestions({ page: Number(page), limit: Number(limit), status, categoryId, }); return { success: true, data: result.items, pagination: result.pagination, error: null }; }); app.get('/:id', async (request) => { const { id } = request.params as { id: string }; const data = await questionService.getQuestionById(id); return { success: true, data, error: null }; }); app.post('/', async (request) => { const parsed = createQuestionSchema.safeParse(request.body); if (!parsed.success) { return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }; } const data = await questionService.createQuestion(parsed.data); return { success: true, data, error: null }; }); app.put('/:id', async (request) => { const { id } = request.params as { id: string }; const parsed = updateQuestionSchema.safeParse(request.body); if (!parsed.success) { return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }; } const data = await questionService.updateQuestion(id, parsed.data); return { success: true, data, error: null }; }); app.patch('/:id/status', async (request, reply) => { const { id } = request.params as { id: string }; const parsed = z.object({ status: z.enum(['draft', 'reviewing', 'published', 'archived']), }).safeParse(request.body); if (!parsed.success) { return reply.status(400).send({ success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }); } try { const data = await questionService.updateQuestionStatus(id, parsed.data.status); return { success: true, data, error: null }; } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg === 'QUESTION_NOT_FOUND') { return reply.status(404).send({ success: false, data: null, error: { code: 'NOT_FOUND', message: '题目不存在' } }); } if (msg.startsWith('INVALID_STATUS_TRANSITION:')) { const [, from, to] = msg.split(':'); return reply.status(400).send({ success: false, data: null, error: { code: 'INVALID_STATUS_TRANSITION', message: `不允许从 ${from} 变更为 ${to}` } }); } throw err; } }); app.delete('/:id', async (request) => { const { id } = request.params as { id: string }; await questionService.archiveQuestion(id); return { success: true, data: null, error: null }; }); app.post('/batch-publish', async (request) => { const parsed = batchIdsSchema.safeParse(request.body); if (!parsed.success) { return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }; } const data = await questionService.batchPublish(parsed.data.ids); return { success: true, data, error: null }; }); app.post('/batch-archive', async (request) => { const parsed = batchIdsSchema.safeParse(request.body); if (!parsed.success) { return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }; } const data = await questionService.batchArchive(parsed.data.ids); return { success: true, data, error: null }; }); app.post('/batch-delete', async (request) => { const parsed = batchIdsSchema.safeParse(request.body); if (!parsed.success) { return { success: false, data: null, error: { code: 'VALIDATION_ERROR', message: parsed.error.issues[0]?.message } }; } const data = await questionService.batchArchive(parsed.data.ids); return { success: true, data, error: null }; }); }