Files
pagodera/app/api/listings/[id]/intentions/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

243 lines
7.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { secureLogger } from "@/lib/secure-logger";
import { requireAuth } from "@/lib/auth/middleware";
import { getListingById } from "@/lib/prisma/queries/listings";
import {
createPurchaseIntention,
getPurchaseIntentionsByListing,
hasPendingIntention,
} from "@/lib/prisma/queries/purchase-intentions";
import { createNotification } from "@/lib/prisma/queries/notifications";
import { trackEvent } from "@/lib/prisma/queries/telemetry";
/**
* POST /api/listings/[id]/intentions
* Cria uma nova intenção de compra
*/
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 { payment_method, buyer_message } = body;
if (!payment_method || !["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method)) {
return NextResponse.json(
{ success: false, error: "Método de pagamento inválido" },
{ status: 400 }
);
}
// Buscar listing
const listing = await getListingById(listingId);
if (!listing) {
return NextResponse.json(
{ success: false, error: "Anúncio não encontrado" },
{ status: 404 }
);
}
// Validar se não é o vendedor
if (listing.seller_id === auth.userId) {
return NextResponse.json(
{ success: false, error: "Você não pode criar uma intenção de compra para seu próprio anúncio" },
{ status: 403 }
);
}
// Validar se está ativo
if (listing.listing_status !== "active") {
return NextResponse.json(
{ success: false, error: "Anúncio não está disponível para compra" },
{ status: 400 }
);
}
// Validar se é leilão (leilões não podem ter intenções, apenas lances)
if (listing.is_auction) {
return NextResponse.json(
{ success: false, error: "Este item está em leilão. Use a funcionalidade de lance." },
{ status: 400 }
);
}
// Validar método de pagamento disponível
const availableMethods: string[] = [];
if (listing.price_pix) availableMethods.push("pix");
if (listing.price_tibia_coins) availableMethods.push("tibia_coins");
if (listing.price_gold_coins) availableMethods.push("gold_coins");
if (listing.trade_item_id) availableMethods.push("trade");
if (!availableMethods.includes(payment_method)) {
return NextResponse.json(
{
success: false,
error: `Método de pagamento "${payment_method}" não está disponível para este anúncio`,
available_methods: availableMethods,
},
{ status: 400 }
);
}
// Verificar se já existe uma intenção pendente
const alreadyHasPending = await hasPendingIntention(listingId, auth.userId);
if (alreadyHasPending) {
return NextResponse.json(
{ success: false, error: "Você já possui uma intenção de compra pendente para este anúncio" },
{ status: 400 }
);
}
// Criar intenção de compra
const intention = await createPurchaseIntention({
listing_id: listingId,
buyer_id: auth.userId,
payment_method: payment_method as any,
buyer_message: buyer_message || undefined,
});
// Criar notificação para o vendedor
await createNotification({
user_id: listing.seller_id,
notification_type: "purchase",
title: "Nova intenção de compra!",
message: `Alguém manifestou interesse em comprar seu item "${listing.item_name}" por ${payment_method.toUpperCase()}`,
related_id: listingId,
send_whatsapp: false,
});
// Registrar telemetria
await trackEvent({
event_type: "purchase_intention_created",
ad_id: listingId,
user_id: auth.userId,
metadata: {
payment_method,
listing_price_pix: listing.price_pix ? Number(listing.price_pix) : null,
listing_price_tc: listing.price_tibia_coins || null,
listing_price_gp: listing.price_gold_coins ? Number(listing.price_gold_coins) : null,
},
});
secureLogger.info("✅ Intenção de compra criada com sucesso", {
userId: auth.userId,
listingId,
intentionId: intention.intention_id,
paymentMethod: payment_method,
});
return NextResponse.json(
{
success: true,
intention,
message: "Intenção de compra criada com sucesso. O vendedor será notificado.",
},
{ status: 201 }
);
} catch (error) {
secureLogger.error("❌ Erro ao criar intenção de compra", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{ success: false, error: "Erro ao criar intenção de compra" },
{ status: 500 }
);
}
}
/**
* GET /api/listings/[id]/intentions
* Lista intenções de compra de um anúncio (apenas vendedor)
*/
export async function GET(
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 }
);
}
// Buscar listing para verificar se o usuário é o vendedor
const listing = await getListingById(listingId);
if (!listing) {
return NextResponse.json(
{ success: false, error: "Anúncio não encontrado" },
{ status: 404 }
);
}
// Apenas o vendedor pode ver as intenções
if (listing.seller_id !== auth.userId) {
return NextResponse.json(
{ success: false, error: "Você não tem permissão para ver as intenções deste anúncio" },
{ status: 403 }
);
}
// Buscar parâmetros de filtro
const { searchParams } = new URL(request.url);
const status = searchParams.get("status") as any;
const limit = parseInt(searchParams.get("limit") || "50", 10);
// Buscar intenções
const intentions = await getPurchaseIntentionsByListing(listingId, {
status,
limit,
});
// Registrar telemetria
await trackEvent({
event_type: "purchase_intention_viewed",
ad_id: listingId,
user_id: auth.userId,
metadata: {
intentions_count: intentions.length,
},
});
return NextResponse.json(
{
success: true,
intentions,
count: intentions.length,
},
{ status: 200 }
);
} catch (error) {
secureLogger.error("❌ Erro ao buscar intenções de compra", {
error: error instanceof Error ? error.message : "Erro desconhecido",
stack: error instanceof Error ? error.stack : undefined,
});
return NextResponse.json(
{ success: false, error: "Erro ao buscar intenções de compra" },
{ status: 500 }
);
}
}