Files
pagodera/app/api/listings/[id]/route.ts
T
Evandro Fonseca Junior f35fee944d 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.
2025-11-05 04:38:07 -03:00

302 lines
7.4 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { secureLogger } from "@/lib/secure-logger";
import { requireAuth, optionalAuth } from "@/lib/auth/middleware";
import {
getListingById,
updateListing,
deleteListing,
} from "@/lib/prisma/queries/listings";
import { updateListingSchema } from "@/lib/validation-schemas";
/**
* GET /api/listings/[id]
* Busca anúncio por ID
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{
success: false,
error: "ID de anúncio inválido",
},
{ status: 400 }
);
}
const listing = await getListingById(listingId);
if (!listing) {
return NextResponse.json(
{
success: false,
error: "Anúncio não encontrado",
},
{ status: 404 }
);
}
return NextResponse.json(
{
success: true,
listing,
},
{ status: 200 }
);
} catch (error) {
secureLogger.error("❌ Erro ao buscar anúncio", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{
success: false,
error: "Erro ao buscar anúncio",
},
{ status: 500 }
);
}
}
/**
* PATCH /api/listings/[id]
* Atualiza anúncio (apenas dono)
*/
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
// Autenticação obrigatória
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) {
return authResult;
}
const auth = authResult;
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{
success: false,
error: "ID de anúncio inválido",
},
{ status: 400 }
);
}
// Verificar se o anúncio existe e pertence ao usuário
const listing = await getListingById(listingId);
if (!listing) {
return NextResponse.json(
{
success: false,
error: "Anúncio não encontrado",
},
{ status: 404 }
);
}
if (listing.seller_id !== auth.userId) {
return NextResponse.json(
{
success: false,
error: "Você não tem permissão para editar este anúncio",
},
{ status: 403 }
);
}
const body = await request.json();
// Validação com Zod
const validationResult = updateListingSchema.safeParse(body);
if (!validationResult.success) {
const errors = validationResult.error.issues.map((err) => ({
field: err.path.join("."),
message: err.message,
}));
return NextResponse.json(
{
success: false,
error: "Dados de entrada inválidos",
details: errors,
},
{ status: 400 }
);
}
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, updateData);
if (!updatedListing) {
return NextResponse.json(
{
success: false,
error: "Erro ao atualizar anúncio",
},
{ status: 500 }
);
}
secureLogger.info("✅ Anúncio atualizado com sucesso", {
listingId,
userId: auth.userId,
timestamp: new Date().toISOString(),
});
return NextResponse.json(
{
success: true,
listing: updatedListing,
},
{ status: 200 }
);
} catch (error) {
secureLogger.error("❌ Erro ao atualizar anúncio", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{
success: false,
error: "Erro ao atualizar anúncio",
},
{ status: 500 }
);
}
}
/**
* DELETE /api/listings/[id]
* Cancela anúncio (apenas dono)
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
// Autenticação obrigatória
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) {
return authResult;
}
const auth = authResult;
const { id } = await params;
const listingId = parseInt(id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{
success: false,
error: "ID de anúncio inválido",
},
{ status: 400 }
);
}
// Verificar se o anúncio existe e pertence ao usuário
const listing = await getListingById(listingId);
if (!listing) {
return NextResponse.json(
{
success: false,
error: "Anúncio não encontrado",
},
{ status: 404 }
);
}
if (listing.seller_id !== auth.userId) {
return NextResponse.json(
{
success: false,
error: "Você não tem permissão para cancelar este anúncio",
},
{ status: 403 }
);
}
// Cancelar anúncio (soft delete)
const deleted = await deleteListing(listingId);
if (!deleted) {
return NextResponse.json(
{
success: false,
error: "Erro ao cancelar anúncio",
},
{ status: 500 }
);
}
secureLogger.info("✅ Anúncio cancelado com sucesso", {
listingId,
userId: auth.userId,
timestamp: new Date().toISOString(),
});
return NextResponse.json(
{
success: true,
message: "Anúncio cancelado com sucesso",
},
{ status: 200 }
);
} catch (error) {
secureLogger.error("❌ Erro ao cancelar anúncio", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{
success: false,
error: "Erro ao cancelar anúncio",
},
{ status: 500 }
);
}
}