sar / src /app /api /poll /route.ts
Mafia2008's picture
feat: full CORS support + OPTIONS preflight + interactive /api-docs reference page
fdd8ef0
Raw
History Blame Contribute Delete
11.6 kB
// ═══════════════════════════════════════════════════════════════════════════
// 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' });
}