- Add Redis 7 for pub/sub (lobby + chat real-time), rate limiting, caching - Replace SSE DB polling with Redis pub/sub (lobby: instant approval, chat: instant delivery) - Add PgBouncer (transaction mode, 500 client → 25 pool connections) - Chat SSE stream via Redis pub/sub instead of 3s polling - Optimistic UI in ChatPanel (messages appear before server confirms) - Redis-based rate limiter (works across multiple app replicas) - Prisma query optimization (select only needed fields) - Chat message cache in Redis (10s TTL) - Docker Compose: add redis, pgbouncer services with healthchecks - Production: resource limits, 2 app replicas behind Traefik - Update CLAUDE.md, README.md, .env.example, setup.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.2 KiB
TypeScript
78 lines
2.2 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { getSessionFromRequest } from "@/lib/auth-helpers";
|
|
import { hash } from "bcryptjs";
|
|
|
|
const createRoomSchema = z.object({
|
|
name: z.string().min(1).max(255),
|
|
lobbyEnabled: z.boolean().optional().default(true),
|
|
webinarMode: z.boolean().optional().default(false),
|
|
pin: z.string().min(4).max(20).optional(),
|
|
});
|
|
|
|
export async function POST(req: Request) {
|
|
const session = await getSessionFromRequest(req);
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
|
|
if (!user || (user.role !== "HOST" && user.role !== "ADMIN")) {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
|
|
const body = await req.json();
|
|
const parsed = createRoomSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json({ error: parsed.error.message }, { status: 400 });
|
|
}
|
|
|
|
const { name, lobbyEnabled, webinarMode, pin } = parsed.data;
|
|
const pinHash = pin ? await hash(pin, 10) : null;
|
|
|
|
const room = await prisma.room.create({
|
|
data: {
|
|
name,
|
|
lobbyEnabled,
|
|
webinarMode,
|
|
pinHash,
|
|
hostId: user.id,
|
|
},
|
|
});
|
|
|
|
return NextResponse.json(room, { status: 201 });
|
|
}
|
|
|
|
export async function GET(req: Request) {
|
|
const session = await getSessionFromRequest(req);
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({ where: { id: session.user.id } });
|
|
if (!user || (user.role !== "HOST" && user.role !== "ADMIN")) {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
|
|
const where = user.role === "ADMIN" ? {} : { hostId: user.id };
|
|
const rooms = await prisma.room.findMany({
|
|
where,
|
|
orderBy: { createdAt: "desc" },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
code: true,
|
|
status: true,
|
|
lobbyEnabled: true,
|
|
webinarMode: true,
|
|
isLocked: true,
|
|
createdAt: true,
|
|
startedAt: true,
|
|
endedAt: true,
|
|
},
|
|
});
|
|
|
|
return NextResponse.json(rooms);
|
|
}
|