61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
import { getLogoSettings } from "./integration-settings";
|
|
|
|
export type LogoCandidate = { name: string; domain: string; logoUrl: string | null };
|
|
|
|
const DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/i;
|
|
|
|
export function normalizeDomain(domain: string | null | undefined): string | null {
|
|
if (!domain) return null;
|
|
const d = domain.trim().toLowerCase();
|
|
if (!DOMAIN_RE.test(d)) return null;
|
|
return d;
|
|
}
|
|
|
|
/** Baut die öffentliche Logo-URL für eine gespeicherte Domain. Key bleibt serverseitig. */
|
|
export async function logoImageUrl(domain: string | null | undefined): Promise<string | null> {
|
|
const d = normalizeDomain(domain);
|
|
const { publishableKey } = await getLogoSettings();
|
|
if (!d || !publishableKey) return null;
|
|
return `https://img.logo.dev/${d}?token=${publishableKey}&size=128&format=png`;
|
|
}
|
|
|
|
/** Kürzel aus dem Anbieternamen als Fallback, z. B. "Therme Erding" → "TE". */
|
|
export function monogram(name: string): string {
|
|
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
if (parts.length === 0) return "?";
|
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
}
|
|
|
|
/** Reichert ein Voucher-Objekt um die öffentliche logoUrl an (serverseitig). */
|
|
export async function withLogoUrl<T extends { providerDomain: string | null }>(
|
|
v: T
|
|
): Promise<T & { logoUrl: string | null }> {
|
|
return { ...v, logoUrl: await logoImageUrl(v.providerDomain) };
|
|
}
|
|
|
|
/** Sucht Anbieter über Logo.dev (serverseitig, Secret-Key). Gibt max. 8 Treffer zurück. */
|
|
export async function searchLogos(query: string): Promise<LogoCandidate[]> {
|
|
const { secretKey } = await getLogoSettings();
|
|
if (!secretKey) throw new Error("LOGO_DEV_SECRET_KEY fehlt");
|
|
const q = query.trim();
|
|
if (!q) return [];
|
|
const res = await fetch(`https://api.logo.dev/search?q=${encodeURIComponent(q)}`, {
|
|
headers: { Authorization: `Bearer ${secretKey}` },
|
|
});
|
|
if (!res.ok) throw new Error(`Logosuche ${res.status}`);
|
|
const data: unknown = await res.json();
|
|
if (!Array.isArray(data)) return [];
|
|
const out: LogoCandidate[] = [];
|
|
for (const item of data) {
|
|
if (typeof item !== "object" || item === null) continue;
|
|
const rec = item as Record<string, unknown>;
|
|
const domain = normalizeDomain(typeof rec.domain === "string" ? rec.domain : null);
|
|
if (!domain) continue;
|
|
const name = typeof rec.name === "string" && rec.name.trim() ? rec.name.trim() : domain;
|
|
out.push({ name, domain, logoUrl: await logoImageUrl(domain) });
|
|
if (out.length >= 8) break;
|
|
}
|
|
return out;
|
|
}
|