mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 19:59:39 +02:00
Pelias was retired from the Mac mini on 2026-04-28; photon-self (self-hosted Photon on mana-gpu) has been the live primary since then. This removes the now-dead Pelias adapter, config, tests, and the services/mana-geocoding/pelias/ stack — the entire compose file, the geojsonify_place_details.js patch, the setup.sh import script. Provider chain is now `photon-self → photon → nominatim`. The chain keeps its `privacy: 'local' | 'public'` split, sensitive-query blocking, coord quantization, and aggressive caching unchanged. Three direct calls to nominatim.openstreetmap.org that bypassed mana-geocoding now route through the wrapper: - citycorners/add-city + citycorners/cities/[slug]/add use the shared searchAddress() client (browser → same-origin proxy → mana-geocoding → photon-self). - memoro mobile drops its OSM reverse-geocoding fallback entirely; Expo's on-device reverse-geocoding stays as the sole path. Routing through the wrapper would require a memoro-server proxy endpoint — a follow-up if Expo's quality proves insufficient. Other behavioral changes: - CACHE_PUBLIC_TTL_MS dropped from 7d → 1h. The long TTL was a privacy-amplification trick from the Pelias era; with photon-self serving the bulk of traffic, a transient cross-LAN blip was pinning cached fallback answers for days. 1h gives quick recovery. - /health/pelias renamed to /health/photon-self; prometheus blackbox config + status-page generator updated. - mana-geocoding container no longer needs `extra_hosts: host.docker.internal:host-gateway` (was only there for the Pelias-on-host-network era). 113 tests passing. CLAUDE.md rewritten to reflect the post-Pelias architecture. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
/**
|
|
* App factory — separated from index.ts so tests can import without
|
|
* triggering the production bootstrap.
|
|
*/
|
|
|
|
import { Hono } from 'hono';
|
|
import { cors } from 'hono/cors';
|
|
import type { Config } from './config';
|
|
import { RateLimiter } from './lib/rate-limiter';
|
|
import { ProviderChain } from './providers/chain';
|
|
import { NominatimProvider } from './providers/nominatim';
|
|
import { PhotonProvider } from './providers/photon';
|
|
import type { GeocodingProvider, ProviderName } from './providers/types';
|
|
import { createGeocodeRoutes } from './routes/geocode';
|
|
import { createHealthRoutes } from './routes/health';
|
|
|
|
export function createApp(config: Config): Hono {
|
|
const chain = createChain(config);
|
|
|
|
const app = new Hono();
|
|
|
|
app.onError((err, c) => {
|
|
console.error('Unhandled error:', err);
|
|
return c.json({ error: 'internal_error' }, 500);
|
|
});
|
|
|
|
app.use(
|
|
'*',
|
|
cors({
|
|
origin: config.cors.origins,
|
|
credentials: true,
|
|
})
|
|
);
|
|
|
|
app.route('/health', createHealthRoutes(config, chain));
|
|
app.route('/api/v1/geocode', createGeocodeRoutes(config, chain));
|
|
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Build the provider chain from config. The order of `config.providers.enabled`
|
|
* is honored — providers earlier in the list are tried first. A disabled
|
|
* provider is simply not registered, not skipped at runtime.
|
|
*/
|
|
export function createChain(config: Config): ProviderChain {
|
|
const built = new Map<ProviderName, GeocodingProvider>();
|
|
|
|
// Self-hosted Photon (mana-gpu). Only registered when the env-var is set
|
|
// — without it the chain runs on public providers only. Once the GPU
|
|
// server is running Photon, flip PHOTON_SELF_API_URL on and this
|
|
// becomes the primary provider.
|
|
if (config.photonSelf.apiUrl) {
|
|
built.set(
|
|
'photon-self',
|
|
new PhotonProvider({
|
|
apiUrl: config.photonSelf.apiUrl,
|
|
timeoutMs: config.providers.timeoutMs,
|
|
name: 'photon-self',
|
|
privacy: 'local',
|
|
})
|
|
);
|
|
}
|
|
|
|
built.set(
|
|
'photon',
|
|
new PhotonProvider({
|
|
apiUrl: config.photon.apiUrl,
|
|
timeoutMs: config.providers.timeoutMs,
|
|
// name + privacy default to 'photon' / 'public' — public komoot
|
|
// is the always-on safety net behind self-hosted.
|
|
})
|
|
);
|
|
|
|
const nominatimLimiter = new RateLimiter(config.nominatim.intervalMs);
|
|
built.set(
|
|
'nominatim',
|
|
new NominatimProvider(
|
|
{
|
|
apiUrl: config.nominatim.apiUrl,
|
|
userAgent: config.nominatim.userAgent,
|
|
timeoutMs: config.providers.timeoutMs,
|
|
},
|
|
nominatimLimiter
|
|
)
|
|
);
|
|
|
|
const ordered = config.providers.enabled
|
|
.map((name) => built.get(name))
|
|
.filter((p): p is GeocodingProvider => p !== undefined);
|
|
|
|
return new ProviderChain({
|
|
providers: ordered,
|
|
healthCacheMs: config.providers.healthCacheMs,
|
|
log: (level, msg, meta) => {
|
|
if (level === 'warn') {
|
|
console.warn('[geocoding-chain]', msg, meta ?? '');
|
|
} else {
|
|
console.log('[geocoding-chain]', msg, meta ?? '');
|
|
}
|
|
},
|
|
});
|
|
}
|