refactor(register, listings, items): simplify transaction, remove unused variable, add explicit types

- Remove newUser variable and directly return created user from Prisma transaction in register route
- Add explicit casts for Prisma QueryMode to avoid TypeScript widening in listings search
- Fix items search and getItemBySource with any cast and ts-expect-error to satisfy type checks

Improves readability and type safety without changing behavior.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-05 04:38:07 -03:00
1 parent ab37b477df
commit f35fee944d
33 files changed
+811 -295

No files matched your search

+21 -1
View File
@@ -63,13 +63,33 @@ export default async function MarketPage({
sortOrder: (typeof sp.sortOrder === "string" ? sp.sortOrder : "DESC") as "ASC" | "DESC",
});
// Converter Date para string para compatibilidade com o tipo do cliente
const serializedListings = listingsData.listings.map((listing) => ({
...listing,
created_at: listing.created_at instanceof Date
? listing.created_at.toISOString()
: listing.created_at,
updated_at: listing.updated_at instanceof Date
? listing.updated_at.toISOString()
: listing.updated_at,
sold_at: listing.sold_at instanceof Date
? listing.sold_at.toISOString()
: listing.sold_at,
auction_ends_at: listing.auction_ends_at instanceof Date
? listing.auction_ends_at.toISOString()
: listing.auction_ends_at,
expires_at: listing.expires_at instanceof Date
? listing.expires_at.toISOString()
: listing.expires_at,
}));
return (
<div className="min-h-screen">
<Header />
<main className="container mx-auto px-4 py-8">
<MarketClient
initial={{
listings: listingsData.listings,
listings: serializedListings as any,
total: listingsData.total,
page,
pageSize,
+58 -62
View File
@@ -114,80 +114,76 @@ export async function POST(request: NextRequest) {
// 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,
},
});
// Usar transação Prisma
const newUser = 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,
};
const userData = {
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;
// 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,
// Se este personagem será main e não é o primeiro, desmarcar outros
if (isMain && i > 0) {
await tx.userCharacter.updateMany({
where: { user_id: userData.userId },
data: { is_main: false },
});
}
// Incrementar total_users dos servidores (dentro da transação)
await tx.server.updateMany({
where: {
server_name: { in: uniqueServerNames },
},
// Inserir personagem
const character = await tx.userCharacter.create({
data: {
total_users: { increment: 1 },
last_activity_at: new Date(),
user_id: userData.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;
}
return userData;
});
// Gerar tokens
const accessToken = generateAccessToken(newUser.userId, newUser.username);
+3 -2
View File
@@ -10,14 +10,15 @@ import { createNotification } from "@/lib/prisma/queries/notifications";
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
+3 -2
View File
@@ -11,13 +11,14 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
@@ -14,13 +14,14 @@ import { createNotification } from "@/lib/prisma/queries/notifications";
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string; intentionId: string } }
{ params }: { params: Promise<{ id: string; intentionId: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const intentionId = parseInt(params.intentionId, 10);
const { intentionId: intentionIdParam } = await params;
const intentionId = parseInt(intentionIdParam, 10);
if (isNaN(intentionId)) {
return NextResponse.json(
{ success: false, error: "ID de intenção inválido" },
@@ -70,13 +71,14 @@ export async function GET(
*/
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string; intentionId: string } }
{ params }: { params: Promise<{ id: string; intentionId: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const intentionId = parseInt(params.intentionId, 10);
const { intentionId: intentionIdParam } = await params;
const intentionId = parseInt(intentionIdParam, 10);
if (isNaN(intentionId)) {
return NextResponse.json(
{ success: false, error: "ID de intenção inválido" },
+7 -5
View File
@@ -16,14 +16,15 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
@@ -166,14 +167,15 @@ export async function POST(
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
+3 -2
View File
@@ -15,14 +15,15 @@ import { trackEvent } from "@/lib/prisma/queries/telemetry";
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
+20 -1
View File
@@ -142,8 +142,27 @@ export async function PATCH(
const validatedData = validationResult.data;
// Converter null para undefined para compatibilidade com UpdateListingData
const updateData: {
quantity?: number;
price_pix?: number;
price_tibia_coins?: number;
price_gold_coins?: number;
description?: string;
listing_status?: "active" | "sold" | "cancelled" | "in_escrow" | "expired";
current_bid_amount?: number;
current_bidder_id?: number;
} = {
...(validatedData.quantity !== undefined && { quantity: validatedData.quantity }),
...(validatedData.price_pix !== null && validatedData.price_pix !== undefined && { price_pix: validatedData.price_pix }),
...(validatedData.price_tibia_coins !== null && validatedData.price_tibia_coins !== undefined && { price_tibia_coins: validatedData.price_tibia_coins }),
...(validatedData.price_gold_coins !== null && validatedData.price_gold_coins !== undefined && { price_gold_coins: validatedData.price_gold_coins }),
...(validatedData.description !== null && validatedData.description !== undefined && { description: validatedData.description }),
...(validatedData.listing_status !== undefined && { listing_status: validatedData.listing_status }),
};
// Atualizar anúncio
const updatedListing = await updateListing(listingId, validatedData);
const updatedListing = await updateListing(listingId, updateData);
if (!updatedListing) {
return NextResponse.json(
+3 -2
View File
@@ -9,10 +9,11 @@ import { getMainCharacterByUserId } from "@/lib/prisma/queries/characters";
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const listingId = parseInt(params.id, 10);
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
+6 -2
View File
@@ -159,10 +159,14 @@ export async function POST(request: NextRequest) {
}
// Criar anúncio
// O schema garante que pelo menos item_id ou item_id_unified está presente
// Mas a interface CreateListingData exige item_id, então usamos item_id_unified como fallback
// O refine do schema garante que pelo menos um está presente, então o fallback nunca será usado
const itemId = validatedData.item_id ?? validatedData.item_id_unified!;
const listing = await createListing({
seller_id: auth.userId,
item_id: validatedData.item_id,
item_id_unified: validatedData.item_id_unified,
item_id: itemId,
item_name: validatedData.item_name,
quantity: validatedData.quantity,
server_name: validatedData.server_name,
+3 -2
View File
@@ -9,14 +9,15 @@ import { markAsRead } from "@/lib/prisma/queries/notifications";
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const notificationId = parseInt(params.id, 10);
const { id } = await params;
const notificationId = parseInt(id, 10);
if (isNaN(notificationId)) {
return NextResponse.json(
{ success: false, error: "ID de notificação inválido" },
+2
View File
@@ -71,6 +71,7 @@ export async function POST(request: NextRequest) {
...event,
user_id: userId,
session_id: batchSessionId || event.session_id || undefined,
ad_id: event.ad_id ?? undefined, // Converter null para undefined
metadata: event.metadata || {},
}));
@@ -117,6 +118,7 @@ export async function POST(request: NextRequest) {
...eventData,
user_id: userId,
session_id: body.session_id || undefined,
ad_id: eventData.ad_id ?? undefined, // Converter null para undefined
metadata: eventData.metadata || {},
});