| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POLL API - Create, vote, and manage polls | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POST /api/poll - Create poll, vote, or end poll | |
| // GET /api/poll?sessionId=xxx - Get all polls 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 POLLS | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // GET /api/poll?sessionId=xxx | |
| // Returns all polls for a session (active and ended) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 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 polls = sessionManager.getPolls(sessionId); | |
| const activePoll = sessionManager.getActivePoll(sessionId); | |
| return NextResponse.json({ | |
| success: true, | |
| data: { | |
| polls, | |
| activePoll, | |
| totalPolls: polls.length | |
| } | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POLL 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 'vote': | |
| return handleVote(sessionId, body); | |
| case 'end': | |
| return handleEnd(sessionId, body); | |
| case 'setCorrectAnswers': | |
| return handleSetCorrectAnswers(sessionId, body); | |
| default: | |
| return NextResponse.json({ | |
| success: false, | |
| error: `Invalid action: ${action}. Valid actions: create, vote, end, setCorrectAnswers` | |
| }, { status: 400 }); | |
| } | |
| } catch (error) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Invalid JSON body' | |
| }, { status: 400 }); | |
| } | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // CREATE POLL | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POST /api/poll | |
| // Body: { | |
| // action: 'create', | |
| // sessionId: string, // Required: Session ID | |
| // question: string, // Required: Poll question | |
| // options: string[], // Required: Array of option texts | |
| // type?: 'single' | 'multiple', // Optional: Vote type (default: 'single') | |
| // correctAnswer?: string, // Optional: Correct answer (for quizzes) | |
| // duration?: number // Optional: Duration in seconds (default: 60) | |
| // } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function handleCreate(sessionId: string, body: any) { | |
| const { question, options, type, correctAnswer, duration } = body; | |
| // Validate required fields | |
| if (!question || typeof question !== 'string') { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: question (string)' | |
| }, { status: 400 }); | |
| } | |
| if (!options || !Array.isArray(options) || options.length < 2) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: options (array with at least 2 items)' | |
| }, { status: 400 }); | |
| } | |
| // Auto-end any existing active poll before creating a new one | |
| const activePoll = sessionManager.getActivePoll(sessionId); | |
| if (activePoll) { | |
| await sessionManager.endPoll(sessionId, activePoll.id); | |
| } | |
| // Create poll | |
| const poll = await sessionManager.createPoll( | |
| sessionId, | |
| question, | |
| options, | |
| type || 'single', | |
| correctAnswer, | |
| duration || 0 // 0 = no auto-end timer | |
| ); | |
| return NextResponse.json({ | |
| success: true, | |
| data: poll, | |
| message: 'Poll created successfully' | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // VOTE ON POLL | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POST /api/poll | |
| // Body: { | |
| // action: 'vote', | |
| // sessionId: string, // Required: Session ID | |
| // pollId: string, // Required: Poll ID | |
| // option: string, // Required: Selected option text | |
| // studentId: string // Required: Student's unique ID | |
| // } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function handleVote(sessionId: string, body: any) { | |
| const { pollId, option, studentId } = body; | |
| // Validate required fields | |
| if (!pollId) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: pollId' | |
| }, { status: 400 }); | |
| } | |
| if (!option) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: option' | |
| }, { status: 400 }); | |
| } | |
| if (!studentId) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: studentId' | |
| }, { status: 400 }); | |
| } | |
| // Vote | |
| const poll = await sessionManager.votePoll(sessionId, pollId, option, studentId); | |
| if (!poll) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Poll not found or inactive' | |
| }, { status: 404 }); | |
| } | |
| // Check if student already voted | |
| if (!poll.votedStudents.includes(studentId)) { | |
| // Vote was recorded | |
| } | |
| return NextResponse.json({ | |
| success: true, | |
| data: { | |
| pollId: poll.id, | |
| results: poll.results, | |
| totalVotes: poll.totalVotes, | |
| voted: poll.votedStudents.includes(studentId) | |
| }, | |
| message: 'Vote recorded successfully' | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // END POLL | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POST /api/poll | |
| // Body: { | |
| // action: 'end', | |
| // sessionId: string, // Required: Session ID | |
| // pollId: string // Required: Poll ID to end | |
| // } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function handleEnd(sessionId: string, body: any) { | |
| const { pollId } = body; | |
| if (!pollId) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Missing required field: pollId' | |
| }, { status: 400 }); | |
| } | |
| await sessionManager.endPoll(sessionId, pollId); | |
| return NextResponse.json({ | |
| success: true, | |
| message: 'Poll ended successfully' | |
| }); | |
| } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // SET CORRECT ANSWERS (Teacher marks correct after poll ends) | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // POST /api/poll | |
| // Body: { action: 'setCorrectAnswers', sessionId, pollId, correctOptionIds: string[], correctAnswer?: string } | |
| // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function handleSetCorrectAnswers(sessionId: string, body: any) { | |
| const { pollId, correctOptionIds, correctAnswer } = body; | |
| if (!pollId) { | |
| return NextResponse.json({ success: false, error: 'Missing pollId' }, { status: 400 }); | |
| } | |
| const ids: string[] = correctOptionIds || (correctAnswer ? [correctAnswer] : []); | |
| // Broadcast to all students so they see the correct answer highlighted | |
| await sessionManager.broadcast(sessionId, 'correct_answer', { | |
| pollId, | |
| correctOptionIds: ids, | |
| correctAnswer: correctAnswer || ids[0] || null, | |
| timestamp: new Date().toISOString(), | |
| }); | |
| return NextResponse.json({ success: true, message: 'Correct answers broadcast to students' }); | |
| } | |