Files
CashuMints.space/web/src/lib/api.ts
T
michilisandCursor c74c7fc187 Ship LNURL mint pages, indexing UI, and reviews rewrite.
Add lnurl list/detail routes, OG fixtures, i18n strings, and the write/
index client flows so the site surfaces the new mint type end to end.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 03:44:43 +02:00

85 lines
4.0 KiB
TypeScript

import { ECOSYSTEMS } from '@cashumints/shared';
import type {
FedimintDetail, Health, LnurlDetail, MintDetail, MintListItem, Stats,
} from '@cashumints/shared';
/**
* Build-time API base. Islands use PUBLIC_API_BASE (see api-client.ts) so the browser
* can be pointed at a different origin than the build machine.
*/
export const API_URL = import.meta.env['API_URL'] ?? process.env['API_URL'] ?? 'http://127.0.0.1:8787';
async function get<T>(path: string): Promise<T> {
const res = await fetch(`${API_URL}${path}`, { headers: { Accept: 'application/json' } });
if (!res.ok) throw new Error(`${path} responded ${res.status}`);
return (await res.json()) as T;
}
/**
* Build-time fetches. A failure here fails the build on purpose: shipping a site with
* an empty mint list would be worse than not shipping.
*
* `fetchMints` asks for `type=cashu` rather than for everything, which is what keeps
* /mints, the mint pages, the home page grid and the sitemap's mint entries meaning
* exactly what they meant before federations were indexed. Each ecosystem is its own
* call, its own pages and its own count; nothing has to filter a mixed list and nothing
* silently grew when a second or a third was added.
*/
export const fetchMints = (): Promise<MintListItem[]> => get<MintListItem[]>('/api/mints?type=cashu');
export const fetchFedimints = (): Promise<MintListItem[]> =>
get<MintListItem[]>('/api/mints?type=fedimint');
export const fetchMint = (host: string): Promise<MintDetail> =>
get<MintDetail>(`/api/mints/${encodeURIComponent(host)}`);
/** The same endpoint, typed for a federation's flattened fields. */
export const fetchFedimint = (host: string): Promise<FedimintDetail> =>
get<FedimintDetail>(`/api/mints/${encodeURIComponent(host)}`);
export const fetchLnurlMints = (): Promise<MintListItem[]> =>
get<MintListItem[]>('/api/mints?type=lnurl');
/** The same endpoint again, typed for an LNURL mint's flattened fields. */
export const fetchLnurlMint = (host: string): Promise<LnurlDetail> =>
get<LnurlDetail>(`/api/mints/${encodeURIComponent(host)}`);
export const fetchStats = (): Promise<Stats> => get<Stats>('/api/stats');
/**
* Every ecosystem's listing, in `ECOSYSTEMS` order, fetched together.
*
* For the pages that are about *all* of them rather than one — /reviews and the home
* page's review strip — where naming each ecosystem's fetch by hand is exactly the kind
* of list that gets one entry short. The filter pills on /reviews are already built from
* `ECOSYSTEMS`, so before this existed a new ecosystem got a pill that could never match
* anything: the pill came from that list and the data did not. This closes that gap, and
* a fourth ecosystem now needs no edit on either side.
*
* One request per ecosystem rather than a single unfiltered `/api/mints`, deliberately:
* the per-type responses are what every other page already asks for, so they are warm in
* the API's own path, and a caller gets them back already grouped.
*/
export async function fetchAllListings(): Promise<MintListItem[][]> {
return Promise.all(
ECOSYSTEMS.map((type) => get<MintListItem[]>(`/api/mints?type=${encodeURIComponent(type)}`)),
);
}
/** Health is allowed to be down at build time: the ticker just renders without it. */
export async function fetchHealth(): Promise<Health | null> {
try {
return await get<Health>('/api/health');
} catch {
return null;
}
}
/**
* Browser-facing API origin baked into the prerendered markup. Empty means same origin:
* icons resolve to /icons on whatever host serves the page, which the dev server proxy
* and the nginx config in the README both forward to the API. Never fall back to
* API_URL here — that is a build-machine address (127.0.0.1) and a visitor's browser
* cannot reach it. Set PUBLIC_API_URL only when the API lives on its own origin.
*/
export const PUBLIC_API_URL = process.env['PUBLIC_API_URL'] ?? '';
/** URL for a cached mint icon, or null. */
export function iconUrl(icon: string | null): string | null {
return icon ? `${PUBLIC_API_URL}${icon}` : null;
}