import { DISCOVER_BASE_URL } from "./env"; // Maps Minnect's public discovery payload (GET /api/v1/individual/home) to // PUBLIC FIELDS ONLY. The mappers build each object from an explicit // allow-list, so phone, DOB, zip, email, gender, KYC/Stripe fields, // is_blocking, scores and rates can never leak through. No token is sent or logged. export interface DiscoverExpert { id: string | number; slug: string | null; displayName: string; byline: string | null; pictureUrl: string | null; rating: number | null; reviewsCount: number | null; available: boolean | null; location: { city: string | null; country: string | null }; } export interface DiscoverMinnect { id: string | number; question: string | null; views: number | null; thumbnailUrl: string | null; audio: { url: string | null; duration: number | null; waveform: number[] | string | null; }; expert: { slug: string | null; displayName: string; pictureUrl: string | null; byline: string | null; rating: number | null; }; } export interface DiscoverCategory { id: string | number; name: string; icon: string | null; sortOrder: number; } export interface DiscoverData { featuredExperts: DiscoverExpert[]; trendingMinnects: DiscoverMinnect[]; categories: DiscoverCategory[]; } export type DiscoverResult = | { ok: true; data: DiscoverData } | { ok: false; status: 502; error: "discover_unavailable" }; type Rec = Record; const rec = (v: unknown): Rec => v && typeof v === "object" && !Array.isArray(v) ? (v as Rec) : {}; const arr = (v: unknown): unknown[] => (Array.isArray(v) ? v : []); const str = (v: unknown): string | null => typeof v === "string" && v.trim() ? v : null; const num = (v: unknown): number | null => typeof v === "number" && Number.isFinite(v) ? v : null; const bool = (v: unknown): boolean | null => (typeof v === "boolean" ? v : null); const id = (v: unknown): string | number | null => typeof v === "string" && v ? v : typeof v === "number" ? v : null; function displayName(r: Rec): string { return [str(r.first_name), str(r.last_name)].filter(Boolean).join(" "); } export function mapExpert(raw: unknown): DiscoverExpert | null { const r = rec(raw); const eid = id(r.id); if (eid === null) return null; return { id: eid, slug: str(r.slug), displayName: displayName(r), byline: str(r.byline), pictureUrl: str(r.url_picture), rating: num(r.rating), reviewsCount: num(r.reviews_count), available: bool(r.available), location: { city: str(r.city), country: str(r.country) }, }; } function waveform(v: unknown): number[] | string | null { if (Array.isArray(v)) return v.filter((n): n is number => typeof n === "number"); return str(v); } export function mapMinnect(raw: unknown): DiscoverMinnect | null { const r = rec(raw); const mid = id(r.id); if (mid === null) return null; const answer = rec(r.answer); const expert = rec(r.expert); return { id: mid, question: str(r.question_text), views: num(r.views), thumbnailUrl: str(rec(r.thumbnail).url), audio: { url: str(answer.url), duration: num(answer.duration), waveform: waveform(answer.waveform_data), }, expert: { slug: str(expert.slug), displayName: displayName(expert), pictureUrl: str(expert.url_picture), byline: str(expert.byline), rating: num(expert.rating), }, }; } export function mapCategory(raw: unknown): DiscoverCategory | null { const r = rec(raw); const cid = id(r.id); const name = str(r.name); if (cid === null || !name || r.active === false) return null; return { id: cid, name, icon: str(r.icon), sortOrder: num(r.sort_order) ?? 0 }; } export function mapDiscover(body: unknown): DiscoverData { const data = rec(rec(body).data); const keep = (x: T | null): x is T => x !== null; return { featuredExperts: arr(data.featured_experts).map(mapExpert).filter(keep), trendingMinnects: arr(data.trending_conversations).map(mapMinnect).filter(keep), categories: arr(data.trending_categories) .map(mapCategory) .filter(keep) .sort((a, b) => a.sortOrder - b.sortOrder), }; } const UNAVAILABLE: DiscoverResult = { ok: false, status: 502, error: "discover_unavailable", }; /** Fetch and map the public discovery data. Never throws; upstream failure -> 502. */ export async function getDiscover( fetchImpl: typeof fetch = fetch, ): Promise { try { const res = await fetchImpl(`${DISCOVER_BASE_URL}/api/v1/individual/home`, { headers: { Accept: "application/json" }, cache: "no-store", }); if (!res.ok) return UNAVAILABLE; const body: unknown = await res.json(); return { ok: true, data: mapDiscover(body) }; } catch { return UNAVAILABLE; } }