Files
2025-11-11 03:08:36 -03:00

331 lines
7.9 KiB
TypeScript

import { type NextRequest, NextResponse } from "next/server";
import { optionalAuth, requireAuth } from "@/lib/auth/middleware";
import {
deleteListing,
getListingById,
updateListing,
} from "@/lib/prisma/queries/listings";
import { hasPendingIntention } from "@/lib/prisma/queries/purchase-intentions";
import { secureLogger } from "@/lib/secure-logger";
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 auth = await optionalAuth(request);
const { id } = await params;
const listingId = parseInt(id, 10);
if (Number.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 },
);
}
const hasPendingForUser = auth?.userId
? await hasPendingIntention(listingId, auth.userId)
: false;
return NextResponse.json(
{
success: true,
listing: {
...listing,
has_pending_intention_for_current_user: hasPendingForUser,
},
},
{ 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 (Number.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 (Number.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 },
);
}
}