sar / src /app /api /doubt /route.ts
Mafia2008's picture
feat: full CORS support + OPTIONS preflight + interactive /api-docs reference page
fdd8ef0
Raw
History Blame Contribute Delete
10.7 kB
// ═══════════════════════════════════════════════════════════════════════════
// DOUBT API - Submit and resolve student doubts
// ═══════════════════════════════════════════════════════════════════════════
// POST /api/doubt - Create doubt or solve doubt
// GET /api/doubt?sessionId=xxx - Get all doubts for a session
// ═══════════════════════════════════════════════════════════════════════════
import { NextRequest, NextResponse } from 'next/server';
import { sessionManager } from '@/lib/session-manager';
export { OPTIONS } from '@/lib/cors';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
// ═══════════════════════════════════════════════════════════════════════════
// GET DOUBTS
// ═══════════════════════════════════════════════════════════════════════════
// GET /api/doubt?sessionId=xxx
// Returns all doubts for a session
// ═══════════════════════════════════════════════════════════════════════════
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const sessionId = searchParams.get('sessionId');
if (!sessionId) {
return NextResponse.json({
success: false,
error: 'Missing required parameter: sessionId'
}, { status: 400 });
}
const session = sessionManager.getSession(sessionId);
if (!session) {
return NextResponse.json({
success: false,
error: 'Session not found'
}, { status: 404 });
}
const doubts = sessionManager.getDoubts(sessionId);
// Group by status
const pending = doubts.filter(d => d.status === 'pending');
const inProgress = doubts.filter(d => d.status === 'in_progress');
const solved = doubts.filter(d => d.status === 'solved');
return NextResponse.json({
success: true,
data: {
doubts,
summary: {
total: doubts.length,
pending: pending.length,
inProgress: inProgress.length,
solved: solved.length
}
}
});
}
// ═══════════════════════════════════════════════════════════════════════════
// DOUBT ACTIONS
// ═══════════════════════════════════════════════════════════════════════════
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { action, sessionId } = body;
// Validate sessionId
if (!sessionId) {
return NextResponse.json({
success: false,
error: 'Missing required field: sessionId'
}, { status: 400 });
}
// Check session exists
const session = sessionManager.getSession(sessionId);
if (!session) {
return NextResponse.json({
success: false,
error: 'Session not found'
}, { status: 404 });
}
switch (action) {
case 'create':
return handleCreate(sessionId, body);
case 'solve':
return handleSolve(sessionId, body);
case 'update':
return handleUpdate(sessionId, body);
default:
return NextResponse.json({
success: false,
error: `Invalid action: ${action}. Valid actions: create, solve, update`
}, { status: 400 });
}
} catch (error) {
return NextResponse.json({
success: false,
error: 'Invalid JSON body'
}, { status: 400 });
}
}
// ═══════════════════════════════════════════════════════════════════════════
// CREATE DOUBT
// ═══════════════════════════════════════════════════════════════════════════
// POST /api/doubt
// Body: {
// action: 'create',
// sessionId: string, // Required: Session ID
// studentId: string, // Required: Student's unique ID
// studentName: string, // Required: Student's display name
// question: string, // Required: The doubt/question
// image?: string, // Optional: Base64 encoded image
// slideNumber?: number // Optional: Slide number where doubt occurred
// }
// ═══════════════════════════════════════════════════════════════════════════
async function handleCreate(sessionId: string, body: any) {
const { studentId, studentName, question, image, slideNumber } = body;
// Validate required fields
if (!studentId) {
return NextResponse.json({
success: false,
error: 'Missing required field: studentId'
}, { status: 400 });
}
if (!studentName) {
return NextResponse.json({
success: false,
error: 'Missing required field: studentName'
}, { status: 400 });
}
if (!question || typeof question !== 'string' || question.trim().length === 0) {
return NextResponse.json({
success: false,
error: 'Missing required field: question'
}, { status: 400 });
}
// Validate image if provided
if (image && !image.startsWith('data:image/')) {
return NextResponse.json({
success: false,
error: 'Invalid image format. Must be base64 data URL starting with "data:image/"'
}, { status: 400 });
}
// Create doubt
const doubt = await sessionManager.createDoubt(
sessionId,
studentId,
studentName,
question.trim(),
image,
slideNumber
);
return NextResponse.json({
success: true,
data: doubt,
message: 'Doubt submitted successfully'
});
}
// ═══════════════════════════════════════════════════════════════════════════
// SOLVE DOUBT
// ═══════════════════════════════════════════════════════════════════════════
// POST /api/doubt
// Body: {
// action: 'solve',
// sessionId: string, // Required: Session ID
// doubtId: string, // Required: Doubt ID to solve
// annotatedImage?: string, // Optional: Base64 annotated image
// teacherResponse?: string // Optional: Text response from teacher
// }
// ═══════════════════════════════════════════════════════════════════════════
async function handleSolve(sessionId: string, body: any) {
const { doubtId, annotatedImage, teacherResponse } = body;
// Validate required fields
if (!doubtId) {
return NextResponse.json({
success: false,
error: 'Missing required field: doubtId'
}, { status: 400 });
}
// Validate image if provided
if (annotatedImage && !annotatedImage.startsWith('data:image/')) {
return NextResponse.json({
success: false,
error: 'Invalid annotatedImage format. Must be base64 data URL'
}, { status: 400 });
}
// Solve doubt
const doubt = await sessionManager.solveDoubt(
sessionId,
doubtId,
annotatedImage,
teacherResponse
);
if (!doubt) {
return NextResponse.json({
success: false,
error: 'Doubt not found'
}, { status: 404 });
}
return NextResponse.json({
success: true,
data: doubt,
message: 'Doubt resolved successfully'
});
}
// ═══════════════════════════════════════════════════════════════════════════
// UPDATE DOUBT STATUS
// ═══════════════════════════════════════════════════════════════════════════
// POST /api/doubt
// Body: {
// action: 'update',
// sessionId: string, // Required: Session ID
// doubtId: string, // Required: Doubt ID
// status: 'in_progress' // Required: New status
// }
// ═══════════════════════════════════════════════════════════════════════════
async function handleUpdate(sessionId: string, body: any) {
const { doubtId, status } = body;
if (!doubtId) {
return NextResponse.json({
success: false,
error: 'Missing required field: doubtId'
}, { status: 400 });
}
if (!status || !['pending', 'in_progress', 'solved'].includes(status)) {
return NextResponse.json({
success: false,
error: 'Invalid status. Must be: pending, in_progress, or solved'
}, { status: 400 });
}
// Get doubts
const doubts = sessionManager.getDoubts(sessionId);
const doubt = doubts.find(d => d.id === doubtId);
if (!doubt) {
return NextResponse.json({
success: false,
error: 'Doubt not found'
}, { status: 404 });
}
// Update status
doubt.status = status as any;
// Broadcast update
await sessionManager.broadcast(sessionId, 'doubt_updated', doubt);
return NextResponse.json({
success: true,
data: doubt,
message: 'Doubt status updated'
});
}