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)
This commit is contained in:
1 parent
55eebde0ba
commit
ab37b477df
106 files changed
+15261
-301
No files matched your search
+95
-2
@@ -1,14 +1,105 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { getItemById } from "@/lib/items-db";
|
||||
import { getItemByIdUnified } from "@/lib/db/queries/items";
|
||||
import { buildItemImagePath } from "@/lib/item-images";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const relPath = searchParams.get("path") || "";
|
||||
const itemId = searchParams.get("item");
|
||||
const itemName = searchParams.get("name"); // Nome do item para validação (opcional)
|
||||
|
||||
let imagePath: string | null = null;
|
||||
|
||||
// Se foi fornecido um item ID, buscar o item no banco
|
||||
if (itemId) {
|
||||
try {
|
||||
const itemIdNum = parseInt(itemId, 10);
|
||||
if (!isNaN(itemIdNum)) {
|
||||
// Tentar primeiro buscar na tabela unificada PostgreSQL
|
||||
// Se a tabela não existir, retornará null silenciosamente
|
||||
let item: any = null;
|
||||
item = await getItemByIdUnified(itemIdNum);
|
||||
|
||||
// Se encontrou na tabela unificada, validar o nome se fornecido
|
||||
if (item && itemName && item.item_name) {
|
||||
if (item.item_name.toLowerCase() !== itemName.toLowerCase()) {
|
||||
// Nome não bate, ignorar este resultado
|
||||
item = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Se não encontrou na tabela unificada, tentar SQLite legado
|
||||
// Passar itemName para buscar por ID E NOME (evita ambiguidade)
|
||||
if (!item) {
|
||||
item = getItemById(itemIdNum, itemName || undefined);
|
||||
}
|
||||
|
||||
if (item) {
|
||||
// PRIORIDADE: 1. image_local_path (local), 2. image_url (externa), 3. image (fallback)
|
||||
const imageLocalPath = item.image_local_path || "";
|
||||
const imageUrl = item.image_url || "";
|
||||
const image = item.image || "";
|
||||
|
||||
// Tentar primeiro a imagem local
|
||||
if (imageLocalPath) {
|
||||
const normalizedPath = imageLocalPath.replace(/\\/g, "/");
|
||||
const category = (item.categoria || item.category || "other").toLowerCase();
|
||||
|
||||
// Se já for um caminho completo (começa com data/), usar diretamente
|
||||
if (normalizedPath.startsWith("data/")) {
|
||||
imagePath = normalizedPath;
|
||||
} else {
|
||||
// Construir o caminho usando a função helper
|
||||
imagePath = buildItemImagePath(category, normalizedPath);
|
||||
}
|
||||
|
||||
// Verificar se o arquivo existe antes de usar
|
||||
if (imagePath) {
|
||||
const baseDir = path.join(process.cwd(), "data", "images");
|
||||
const resolved = path.join(process.cwd(), imagePath);
|
||||
|
||||
if (resolved.startsWith(baseDir)) {
|
||||
try {
|
||||
await fs.promises.access(resolved);
|
||||
// Arquivo existe, usar este caminho
|
||||
} catch {
|
||||
// Arquivo não existe, tentar fallback externo
|
||||
imagePath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Se não encontrou imagem local ou não existe, usar URL externa como fallback
|
||||
if (!imagePath && (imageUrl || image)) {
|
||||
const externalUrl = imageUrl || image;
|
||||
if (externalUrl.startsWith("http://") || externalUrl.startsWith("https://")) {
|
||||
return Response.redirect(externalUrl, 302);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[api/images] Erro ao buscar item:", error);
|
||||
}
|
||||
} else if (relPath) {
|
||||
// Se foi fornecido um path, usar diretamente
|
||||
imagePath = relPath;
|
||||
}
|
||||
|
||||
if (!imagePath) {
|
||||
// Se não foi possível construir o caminho, retornar 404 ao invés de 400
|
||||
// para evitar erros visíveis no console do navegador
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
// Permitir apenas caminhos sob data/images para segurança
|
||||
const baseDir = path.join(process.cwd(), "data", "images");
|
||||
const resolved = path.join(process.cwd(), relPath);
|
||||
const resolved = path.join(process.cwd(), imagePath);
|
||||
|
||||
if (!resolved.startsWith(baseDir)) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
@@ -27,7 +118,9 @@ export async function GET(req: NextRequest) {
|
||||
? "image/jpeg"
|
||||
: "application/octet-stream";
|
||||
return new Response(file, { status: 200, headers: { "Content-Type": contentType } });
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error("[api/images] Erro ao ler arquivo:", error);
|
||||
// Retornar 404 silenciosamente para não gerar erros no console
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user