mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
NEXT_PUBLIC_LOGO_DEV_TOKEN renomeado para LOGO_DEV_TOKEN — lido apenas em runtime no servidor. URL construída nos endpoints /api/logo/mapping e /api/logo/search; cliente nunca recebe o token. Novo server.ts com isLogoDevEnabled() e buildLogoDevUrl(). LogoDevProvider (Context) propaga flag `enabled` para Client Components. Build arg removido do Dockerfile e do workflow docker-publish.yml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
|
import { buildLogoDevUrl } from "@/shared/lib/logo/server";
|
|
|
|
const LOGO_DEV_SEARCH_URL = "https://api.logo.dev/search";
|
|
|
|
interface LogoResult {
|
|
name: string;
|
|
domain: string;
|
|
}
|
|
|
|
interface LogoResultWithUrl extends LogoResult {
|
|
logoUrl: string | null;
|
|
}
|
|
|
|
async function searchByStrategy(
|
|
q: string,
|
|
strategy: "match" | "typeahead",
|
|
secretKey: string,
|
|
): Promise<LogoResult[]> {
|
|
try {
|
|
const url = `${LOGO_DEV_SEARCH_URL}?q=${encodeURIComponent(q)}&strategy=${strategy}`;
|
|
const res = await fetch(url, {
|
|
headers: { Authorization: `Bearer ${secretKey}` },
|
|
next: { revalidate: 3600 },
|
|
});
|
|
if (!res.ok) return [];
|
|
const data = await res.json();
|
|
return Array.isArray(data) ? data : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/logo/search?q={name}
|
|
*
|
|
* Proxy seguro para a Logo.dev Brand Search API.
|
|
* Faz duas buscas paralelas (match + typeahead) e retorna até 20 resultados únicos.
|
|
* Usa LOGO_DEV_SECRET_KEY server-side — nunca exposta ao cliente.
|
|
*/
|
|
export async function GET(request: Request) {
|
|
const session = await getOptionalUserSession();
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Não autorizado." }, { status: 401 });
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const q = searchParams.get("q")?.trim();
|
|
|
|
if (!q) {
|
|
return NextResponse.json(
|
|
{ error: "Parâmetro q obrigatório." },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const secretKey = process.env.LOGO_DEV_SECRET_KEY;
|
|
if (!secretKey) {
|
|
return NextResponse.json(
|
|
{ error: "Logo.dev não configurado." },
|
|
{ status: 503 },
|
|
);
|
|
}
|
|
|
|
// Duas buscas paralelas para maximizar resultados (cada uma retorna até 10)
|
|
const [matchResults, typeaheadResults] = await Promise.all([
|
|
searchByStrategy(q, "match", secretKey),
|
|
searchByStrategy(q, "typeahead", secretKey),
|
|
]);
|
|
|
|
// Mescla e deduplica por domain, mantendo ordem (match tem prioridade)
|
|
const seen = new Set<string>();
|
|
const merged: LogoResultWithUrl[] = [];
|
|
|
|
for (const result of [...matchResults, ...typeaheadResults]) {
|
|
if (!seen.has(result.domain)) {
|
|
seen.add(result.domain);
|
|
// logoUrl é construída server-side com o token — o cliente nunca
|
|
// precisa conhecer LOGO_DEV_TOKEN para renderizar a imagem.
|
|
merged.push({ ...result, logoUrl: buildLogoDevUrl(result.domain) });
|
|
if (merged.length >= 20) break;
|
|
}
|
|
}
|
|
|
|
return NextResponse.json(merged);
|
|
}
|