somehwat AI CSS...but not good at all in teh slightest, logic works

This commit is contained in:
2026-01-28 18:57:52 -06:00
parent bf59155c1e
commit 778a41d1b1
11 changed files with 2104 additions and 140 deletions

View File

@@ -0,0 +1,97 @@
import { NextResponse } from 'next/server';
import { getRoom, buzz, startGame, loadQuestion, startTossupTimer, startBonusTimer, markTossupCorrect, markTossupWrong, markBonusCorrect, markBonusWrong, endGame } from '@/lib/rooms';
import { getRandomQuestion } from '@/lib/scibowl-api';
import { z, ZodError } from 'zod';
const gameActionSchema = z.object({
action: z.enum(['start', 'load_question', 'start_timer', 'buzz', 'tossup_correct', 'tossup_wrong', 'bonus_start_timer', 'bonus_correct', 'bonus_wrong', 'end']),
moderatorId: z.string().optional(),
playerId: z.string().optional(),
});
export async function POST(
req: Request,
{ params }: { params: Promise<{ code: string }> }
) {
try {
const { code } = await params;
const body = await req.json();
const { action, moderatorId, playerId } = gameActionSchema.parse(body);
let success = false;
switch (action) {
case 'start':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = startGame(code, moderatorId);
break;
case 'load_question':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
const question = await getRandomQuestion();
success = loadQuestion(code, moderatorId, question);
break;
case 'start_timer':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = startTossupTimer(code, moderatorId);
break;
case 'buzz':
if (!playerId) return NextResponse.json({ error: 'Missing playerId' }, { status: 400 });
success = buzz(code, playerId);
break;
case 'tossup_correct':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = markTossupCorrect(code, moderatorId);
break;
case 'tossup_wrong':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = markTossupWrong(code, moderatorId);
break;
case 'bonus_start_timer':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = startBonusTimer(code, moderatorId);
break;
case 'bonus_correct':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = markBonusCorrect(code, moderatorId);
break;
case 'bonus_wrong':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = markBonusWrong(code, moderatorId);
break;
case 'end':
if (!moderatorId) return NextResponse.json({ error: 'Missing moderatorId' }, { status: 400 });
success = endGame(code, moderatorId);
break;
}
if (!success) {
return NextResponse.json({ error: 'Action failed' }, { status: 400 });
}
const room = getRoom(code);
return NextResponse.json({
success: true,
gameState: room?.gameState
});
} catch (error: unknown) {
console.error('Game action error:', error);
if (error instanceof ZodError) {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
);
}
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
}

View File

@@ -1,41 +1,102 @@
import { NextResponse } from 'next/server';
import { getRoom, closeRoom, leaveRoom } from '@/lib/rooms';
import { rateLimit, getClientIdentifier } from '@/lib/rate-limit';
import { roomCodeSchema, roomActionSchema } from '@/lib/validation';
import { ZodError } from 'zod';
export async function GET(
req: Request,
{ params }: { params: Promise<{ code: string }> }
) {
const { code } = await params;
const room = getRoom(code);
if (!room) {
return NextResponse.json({ error: 'Room not found' }, { status: 404 });
try {
const clientId = getClientIdentifier(req);
const rateLimitResult = rateLimit(`get:${clientId}`, 500, 60000); // 200 requests per minute
if (!rateLimitResult.success) {
return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
}
const { code } = await params;
const validatedCode = roomCodeSchema.parse(code);
const room = getRoom(validatedCode);
if (!room) {
return NextResponse.json({ error: 'Room not found' }, { status: 404 });
}
return NextResponse.json({
code: room.code,
playerCount: room.players.size,
players: Array.from(room.players.values()).map(p => ({
id: p.id,
name: p.name,
score: p.score,
teamId: p.teamId,
})),
teams: Array.from(room.teams.values()),
gameState: room.gameState,
isActive: room.isActive,
});
} catch (error: unknown) {
console.error('Get room error:', error);
if (error instanceof ZodError) {
return NextResponse.json({ error: 'Invalid room code' }, { status: 400 });
}
return NextResponse.json({ error: 'Failed to get room' }, { status: 400 });
}
return NextResponse.json({
code: room.code,
playerCount: room.players.size,
players: Array.from(room.players.values()),
isActive: room.isActive,
});
}
export async function POST(
req: Request,
{ params }: { params: Promise<{ code: string }> }
) {
const { code } = await params;
const body = await req.json();
if (body.action === 'close') {
const success = closeRoom(code, body.moderatorId);
return NextResponse.json({ success });
try {
const clientId = getClientIdentifier(req);
const rateLimitResult = rateLimit(`action:${clientId}`, 30, 60000);
if (!rateLimitResult.success) {
return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
}
const { code } = await params;
const validatedCode = roomCodeSchema.parse(code);
const body = await req.json();
const validatedAction = roomActionSchema.parse(body);
if (validatedAction.action === 'close') {
if (!validatedAction.moderatorId) {
return NextResponse.json({ error: 'Moderator ID required' }, { status: 400 });
}
const success = closeRoom(validatedCode, validatedAction.moderatorId);
if (!success) {
return NextResponse.json({ error: 'Unauthorized or room not found' }, { status: 403 });
}
return NextResponse.json({ success: true });
}
if (validatedAction.action === 'leave') {
if (!validatedAction.playerId) {
return NextResponse.json({ error: 'Player ID required' }, { status: 400 });
}
const success = leaveRoom(validatedCode, validatedAction.playerId);
return NextResponse.json({ success });
}
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
} catch (error: unknown) {
console.error('Room action error:', error);
if (error instanceof ZodError) {
return NextResponse.json({ error: error.errors[0].message }, { status: 400 });
}
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
if (body.action === 'leave') {
const success = leaveRoom(code, body.playerId);
return NextResponse.json({ success });
}
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
}
}

View File

@@ -1,11 +1,14 @@
import { NextResponse } from 'next/server';
import { createRoom } from '@/lib/rooms';
import { rateLimit, getClientIdentifier } from '@/lib/rate-limit';
import { createRoomSchema } from '@/lib/validation';
import { z } from 'zod';
const createRoomSchema = z.object({
teamMode: z.enum(['1', '2']).optional().default('2'),
});
export async function POST(req: Request) {
try {
// Rate limit: max 5 rooms per IP per hour
const clientId = getClientIdentifier(req);
const rateLimitResult = rateLimit(`create:${clientId}`, 5, 3600000);
@@ -16,18 +19,17 @@ export async function POST(req: Request) {
);
}
// Validate request (empty body is fine for create)
const body = await req.json().catch(() => ({}));
createRoomSchema.parse(body);
const { code, moderatorId } = createRoom();
const { teamMode } = createRoomSchema.parse(body);
const { code, moderatorId } = createRoom(parseInt(teamMode) as 1 | 2);
return NextResponse.json({
code,
moderatorId,
remaining: rateLimitResult.remaining
});
} catch (error: unknown) {
} catch (error) {
console.error('Create room error:', error);
return NextResponse.json(
{ error: 'Failed to create room' },

View File

@@ -1,11 +1,16 @@
import { NextResponse } from 'next/server';
import { joinRoom } from '@/lib/rooms';
import { rateLimit, getClientIdentifier } from '@/lib/rate-limit';
import { joinRoomSchema } from '@/lib/validation';
import { z } from 'zod';
const joinRoomSchema = z.object({
code: z.string().length(6).regex(/^[A-Z0-9]+$/),
playerName: z.string().min(1).max(20).regex(/^[a-zA-Z0-9\s]+$/).transform(val => val.trim()),
teamId: z.enum(['1', '2']),
});
export async function POST(req: Request) {
try {
// Rate limit: max 20 join attempts per IP per minute
const clientId = getClientIdentifier(req);
const rateLimitResult = rateLimit(`join:${clientId}`, 20, 60000);
@@ -19,26 +24,22 @@ export async function POST(req: Request) {
const body = await req.json();
const validatedData = joinRoomSchema.parse(body);
const result = joinRoom(validatedData.code, validatedData.playerName);
const result = joinRoom(
validatedData.code,
validatedData.playerName,
parseInt(validatedData.teamId) as 1 | 2
);
if (!result) {
return NextResponse.json(
{ error: 'Room not found or inactive' },
{ error: 'Room not found, inactive, or game in progress' },
{ status: 404 }
);
}
return NextResponse.json(result);
} catch (error: any) {
} catch (error) {
console.error('Join room error:', error);
if (error.name === 'ZodError') {
return NextResponse.json(
{ error: error.errors[0].message },
{ status: 400 }
);
}
return NextResponse.json(
{ error: 'Invalid request' },
{ status: 400 }