SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
4.0 KB · 68 lines typescript
Raw Blame History
1/** Site-wide constants (URL, name) used by metadata, sitemap and OG images. */2export const SITE_URL = (process.env.NEXT_PUBLIC_SITE_URL ?? 'https://www.countryatlas.co').replace(/\/$/, '');3export const SITE_NAME = 'CountryAtlas';4export const SITE_DOMAIN = 'countryatlas.co';5export const TAGLINE = 'Explore the world through data.';6export const OG_SIZE = { width: 1200, height: 630 } as const;7/** Public API version shown in docs and the footer (path stays /api/v1; the minor tracks additive endpoints). */8export const API_VERSION = '1.1';910type Q = Record<string, string | number | boolean | null | undefined>;11function query(q?: Q): string {12  if (!q) return '';13  const p = new URLSearchParams();14  for (const [k, v] of Object.entries(q)) if (v !== undefined && v !== null && v !== '' && v !== false) p.set(k, String(v));15  const s = p.toString();16  return s ? `?${s}` : '';17}1819/** Route helpers — keep every internal href in one place so the next agent can add pages consistently. */20export const routes = {21  home: () => '/',22  countries: () => '/countries',23  country: (slug: string) => `/countries/${slug}`,24  countryTopic: (slug: string, topic: string) => `/countries/${slug}/${topic}`,25  countryIndicator: (slug: string, topic: string, indicator: string) => `/countries/${slug}/${topic}#${indicator}`,26  countryDownload: (id: string, fmt: 'csv' | 'json' = 'csv') => `/api/v1/countries/${id}/download.${fmt}`,27  compare: (...slugs: string[]) => (slugs.length ? `/compare/${slugs.join('/')}` : '/compare'),28  compareOg: (slugs: string[]) => `/compare/og?slugs=${slugs.join(',')}`,29  compareDownload: (ids: string[], indicators: string[], q: { from?: number | null; to?: number | null } = {}, fmt: 'csv' | 'json' = 'csv') =>30    `/api/v1/compare/download.${fmt}?countries=${ids.join(',')}&indicators=${indicators.join(',')}${q.from != null ? `&from=${q.from}` : ''}${q.to != null ? `&to=${q.to}` : ''}`,31  rankings: () => '/rankings',32  ranking: (indicator: string, q?: { year?: number | null; group?: string | null }) => {33    const p = new URLSearchParams();34    if (q?.year != null) p.set('year', String(q.year));35    if (q?.group && q.group !== 'world') p.set('group', q.group);36    const s = p.toString();37    return `/rankings/${indicator}${s ? `?${s}` : ''}`;38  },39  indicators: (topic?: string) => (topic ? `/indicators?topic=${encodeURIComponent(topic)}` : '/indicators'),40  indicator: (slug: string) => `/indicators/${slug}`,41  indicatorDownload: (slug: string, fmt: 'csv' | 'json' = 'csv') => `/api/v1/indicators/${slug}/download.${fmt}`,42  regions: () => '/regions',43  region: (slug: string) => `/regions/${slug}`,44  regionCompare: (a: string, b: string) => `/regions/compare?a=${a}&b=${b}`,45  sources: () => '/sources',46  source: (id: string) => `/sources/${id}`,47  methodology: () => '/methodology',48  data: () => '/data',49  download: (q?: Q) => `/download${query(q)}`,50  api: () => '/api',51  changes: () => '/changes',52  /** World Explorer — full-viewport map + time slider. */53  explore: (q?: { indicator?: string; year?: number | null; view?: 'map' | 'rank' | 'trend' | 'distribution'; country?: string | null }) => `/explore${query(q)}`,54  trajectories: (q?: { x?: string; y?: string; size?: string; year?: number | null; group?: string | null }) => `/trajectories${query(q)}`,55  scatter: (q?: { x?: string; y?: string; size?: string; year?: number | null; group?: string | null; log?: string }) => `/scatter${query(q)}`,56  finder: (q?: Q) => `/finder${query(q)}`,57  extremes: (q?: { window?: string; topic?: string }) => `/extremes${query(q)}`,58  stories: () => '/stories',59  story: (slug: string) => `/stories/${slug}`,60  updates: () => '/updates',61  peers: (q?: { y?: string; x?: string; year?: number | null }) => `/peers${query(q)}`,62  search: (q: string) => `/search?q=${encodeURIComponent(q)}`,63  apiSwagger: () => '/api/v1/docs',64  apiOpenapi: () => '/api/v1/openapi.json',65  admin: (section?: 'coverage' | 'runs' | 'issues' | 'raw') => (section ? `/admin/${section}` : '/admin'),66  adminLogin: () => '/admin/login',67} as const;68