Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
283 lines
6.2 KiB
TypeScript
283 lines
6.2 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/db/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;
|
|
|
|
// Atualizar anúncio
|
|
const updatedListing = await updateListing(listingId, validatedData);
|
|
|
|
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 }
|
|
);
|
|
}
|
|
}
|
|
|