import { NextRequest, NextResponse } from "next/server"; import { checkRateLimit, getClientIP, createRateLimitResponse, } from "@/lib/rate-limiter"; import { secureLogger } from "@/lib/secure-logger"; import { registerSchema } from "@/lib/validation-schemas"; import { hashPassword } from "@/lib/auth/password"; import { generateAccessToken, generateRefreshToken, } from "@/lib/auth/jwt"; import { getUserByEmail, getUserByUsername, } from "@/lib/prisma/queries/users"; import { isServerActive, incrementUsersForServers, } from "@/lib/prisma/queries/servers"; import { setSession } from "@/lib/redis/session"; import { randomUUID } from "crypto"; import { createUser } from "@/lib/prisma/queries/users"; import { createCharacter } from "@/lib/prisma/queries/characters"; export async function POST(request: NextRequest) { try { // Rate limiting const ip = getClientIP(request); const rateLimit = checkRateLimit(ip, "/api/auth/register"); if (!rateLimit.allowed) { return createRateLimitResponse(rateLimit.resetAt); } const body = await request.json(); secureLogger.info("📥 POST /api/auth/register - Recebido", { ip, timestamp: new Date().toISOString(), }); // Validação com Zod const validationResult = registerSchema.safeParse(body); if (!validationResult.success) { const errors = validationResult.error.issues.map((err) => ({ field: err.path.join("."), message: err.message, })); secureLogger.error("❌ Validação falhou", { errors: JSON.stringify(errors), timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: "Dados de entrada inválidos", details: errors, }, { status: 400 } ); } const validatedData = validationResult.data; // Verificar duplicidade: email const existingEmail = await getUserByEmail(validatedData.email); if (existingEmail) { return NextResponse.json( { success: false, error: "Email já está em uso", }, { status: 409 } ); } // Verificar duplicidade: username const existingUsername = await getUserByUsername(validatedData.username); if (existingUsername) { return NextResponse.json( { success: false, error: "Username já está em uso", }, { status: 409 } ); } // Validar servidores: verificar se existem e estão ativos const serverNames = validatedData.servers.map((s) => s.serverName); const uniqueServerNames = [...new Set(serverNames)]; // Remover duplicatas for (const serverName of uniqueServerNames) { const isActive = await isServerActive(serverName); if (!isActive) { return NextResponse.json( { success: false, error: `Servidor "${serverName}" não existe ou não está ativo`, }, { status: 400 } ); } } // Hash da senha const passwordHash = await hashPassword(validatedData.password); // Criar usuário e personagens em transação atômica usando Prisma const { prisma } = await import("@/lib/prisma/app"); let newUser: { userId: number; username: string; email: string }; const createdCharacters: Array<{ characterName: string; serverName: string }> = []; try { // Usar transação Prisma await prisma.$transaction(async (tx) => { // Criar usuário const user = await tx.user.create({ data: { username: validatedData.username, email: validatedData.email, password_hash: passwordHash, first_name: validatedData.firstName, last_name: validatedData.lastName, phone: validatedData.phone || null, cpf: null, // CPF não é mais obrigatório }, select: { user_id: true, username: true, email: true, }, }); newUser = { userId: user.user_id, username: user.username, email: user.email, }; // Criar personagens (primeiro é main por padrão) for (let i = 0; i < validatedData.servers.length; i++) { const serverData = validatedData.servers[i]; const isMain = i === 0 || serverData.isMain === true; // Se este personagem será main e não é o primeiro, desmarcar outros if (isMain && i > 0) { await tx.userCharacter.updateMany({ where: { user_id: newUser.userId }, data: { is_main: false }, }); } // Inserir personagem const character = await tx.userCharacter.create({ data: { user_id: newUser.userId, character_name: serverData.characterName, server_name: serverData.serverName, is_main: isMain, }, }); createdCharacters.push({ characterName: character.character_name, serverName: character.server_name, }); } // Incrementar total_users dos servidores (dentro da transação) await tx.server.updateMany({ where: { server_name: { in: uniqueServerNames }, }, data: { total_users: { increment: 1 }, last_activity_at: new Date(), }, }); }); } catch (error) { // Prisma já faz rollback automático em caso de erro throw error; } // Gerar tokens const accessToken = generateAccessToken(newUser.userId, newUser.username); const refreshToken = generateRefreshToken( newUser.userId, newUser.username ); // Criar sessão no Redis const sessionId = randomUUID(); await setSession(sessionId, { userId: newUser.userId, username: newUser.username, email: newUser.email, accessToken, createdAt: Date.now(), }); // Criar resposta const response = NextResponse.json( { success: true, user: { userId: newUser.userId, username: newUser.username, email: newUser.email, }, accessToken, }, { status: 201 } ); // Adicionar refresh token e sessionId como cookies httpOnly response.cookies.set("refreshToken", refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 dias path: "/", }); response.cookies.set("sessionId", sessionId, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", maxAge: 60 * 60 * 24 * 7, // 7 dias path: "/", }); secureLogger.info("✅ Usuário registrado com sucesso", { userId: newUser.userId, username: newUser.username, timestamp: new Date().toISOString(), }); return response; } catch (error) { secureLogger.error("❌ Erro ao registrar usuário", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString(), }); return NextResponse.json( { success: false, error: error instanceof Error ? error.message : "Erro interno", }, { status: 500 } ); } }