Files
pagodera/app/api/listings/[id]/bid/route.ts
T
Evandro Fonseca Junior ab37b477df feat: implement market page with QueryProvider and dynamic listings
- wrap root layout with QueryProvider for React Query
- replace market UnderConstruction placeholder with MarketClient and real data fetching
- implement search, pagination, and sort via search params using getActiveListings
- add necessary client components and queries
- update dependencies (e.g., prisma, pkg-types)
2025-11-05 01:58:03 -03:00

119 lines
3.5 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { secureLogger } from "@/lib/secure-logger";
import { requireAuth } from "@/lib/auth/middleware";
import { placeBid } from "@/lib/prisma/queries/listings";
import { createNotification } from "@/lib/prisma/queries/notifications";
/**
* POST /api/listings/[id]/bid
* Coloca um lance em um leilão
*/
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const authResult = await requireAuth(request);
if (authResult instanceof NextResponse) return authResult;
const auth = authResult;
const listingId = parseInt(params.id, 10);
if (isNaN(listingId)) {
return NextResponse.json(
{ success: false, error: "ID de anúncio inválido" },
{ status: 400 }
);
}
const body = await request.json();
const { amount, payment_method } = body;
if (!amount || typeof amount !== "number" || amount <= 0) {
return NextResponse.json(
{ success: false, error: "Valor do lance é obrigatório e deve ser maior que zero" },
{ status: 400 }
);
}
if (!payment_method || !["pix", "tc", "gp"].includes(payment_method)) {
return NextResponse.json(
{ success: false, error: "Método de pagamento inválido" },
{ status: 400 }
);
}
// Colocar lance
const result = await placeBid(listingId, auth.userId, amount, payment_method);
if (!result.success) {
return NextResponse.json(
{ success: false, error: result.error },
{ status: 400 }
);
}
// Criar notificação para o vendedor
await createNotification({
user_id: result.listing!.seller_id,
notification_type: "bid",
title: "Novo lance!",
message: `Novo lance de R$ ${amount.toFixed(2)} no item "${result.listing!.item_name}"`,
related_id: listingId,
send_whatsapp: false,
});
// Se havia um lanceador anterior, notificar que foi superado
if (result.previousBidderId) {
await createNotification({
user_id: result.previousBidderId,
notification_type: "bid",
title: "Lance superado",
message: `Seu lance foi superado no item "${result.listing!.item_name}"`,
related_id: listingId,
send_whatsapp: false,
});
}
// Registrar telemetria
const { trackEvent } = await import("@/lib/prisma/queries/telemetry");
await trackEvent({
event_type: "bid_placed",
ad_id: listingId,
user_id: auth.userId,
metadata: {
bid_amount: amount,
payment_method: payment_method,
previous_bid: result.listing!.current_bid_amount ? Number(result.listing!.current_bid_amount) : null,
min_increment: result.listing!.min_bid_increment ? Number(result.listing!.min_bid_increment) : null,
},
});
secureLogger.info("✅ Lance realizado com sucesso", {
userId: auth.userId,
listingId,
amount,
paymentMethod: payment_method,
});
return NextResponse.json(
{
success: true,
listing: result.listing,
message: "Lance realizado com sucesso",
},
{ status: 200 }
);
} catch (error) {
secureLogger.error("❌ Erro ao processar lance", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{ success: false, error: "Erro ao processar lance" },
{ status: 500 }
);
}
}