mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* refactor: consolidate Upstash helpers and extract DeckGL color config Part 1 — api/_upstash-json.js: add getRedisCredentials, redisPipeline, setCachedData exports. Migrate _oauth-token.js, reverse-geocode.js, health.js, bootstrap.js, seed-health.js, and cache-purge.js off their inline credential/pipeline boilerplate. Part 2 — DeckGLMap.ts: extract getBaseColor, mineralColor, windColor, TC_WIND_COLORS, CII_LEVEL_COLORS into src/config/ files so panels and tests can reuse them without importing DeckGLMap. Surfaced by reviewing nichm/worldmonitor-private fork. * fix(mcp): restore throw-on-Redis-error in fetchOAuthToken; fix health error message _oauth-token.js: readJsonFromUpstash returns null for HTTP errors, but mcp.ts:702 relies on a throw to return 503 (retryable) vs null→401 (re-authenticate). Restore the explicit fetch that throws on !resp.ok, using getRedisCredentials() for credential extraction. health.js: the single null guard produced "Redis not configured" for both missing creds and HTTP failures. Split into two checks so the 503 body correctly distinguishes env config problems from service outages. * fix(upstash): remove dead try/catch in reverse-geocode; atomic SET EX in setCachedData
80 lines
2.5 KiB
JavaScript
80 lines
2.5 KiB
JavaScript
import { getCorsHeaders, isDisallowedOrigin } from './_cors.js';
|
|
import { jsonResponse } from './_json-response.js';
|
|
// @ts-expect-error — JS module, no declaration file
|
|
import { readJsonFromUpstash, setCachedData } from './_upstash-json.js';
|
|
|
|
export const config = { runtime: 'edge' };
|
|
|
|
const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org/reverse';
|
|
const CHROME_UA = 'WorldMonitor/2.0 (https://worldmonitor.app)';
|
|
|
|
export default async function handler(req, ctx) {
|
|
if (isDisallowedOrigin(req))
|
|
return new Response('Forbidden', { status: 403 });
|
|
|
|
const cors = getCorsHeaders(req);
|
|
if (req.method === 'OPTIONS')
|
|
return new Response(null, { status: 204, headers: cors });
|
|
|
|
const url = new URL(req.url);
|
|
const lat = url.searchParams.get('lat');
|
|
const lon = url.searchParams.get('lon');
|
|
|
|
const latN = Number(lat);
|
|
const lonN = Number(lon);
|
|
if (!lat || !lon || Number.isNaN(latN) || Number.isNaN(lonN)
|
|
|| latN < -90 || latN > 90 || lonN < -180 || lonN > 180) {
|
|
return jsonResponse({ error: 'valid lat (-90..90) and lon (-180..180) required' }, 400, cors);
|
|
}
|
|
|
|
const cacheKey = `geocode:${latN.toFixed(1)},${lonN.toFixed(1)}`;
|
|
|
|
const cached = await readJsonFromUpstash(cacheKey, 1500);
|
|
if (cached) {
|
|
return new Response(JSON.stringify(cached), {
|
|
status: 200,
|
|
headers: {
|
|
...cors,
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=3600',
|
|
},
|
|
});
|
|
}
|
|
|
|
try {
|
|
const resp = await fetch(
|
|
`${NOMINATIM_BASE}?lat=${latN}&lon=${lonN}&format=json&zoom=3&accept-language=en`,
|
|
{
|
|
headers: { 'User-Agent': CHROME_UA, Accept: 'application/json' },
|
|
signal: AbortSignal.timeout(8000),
|
|
},
|
|
);
|
|
|
|
if (!resp.ok) {
|
|
return jsonResponse({ error: `Nominatim ${resp.status}` }, 502, cors);
|
|
}
|
|
|
|
const data = await resp.json();
|
|
const country = data.address?.country;
|
|
const code = data.address?.country_code?.toUpperCase();
|
|
|
|
const result = { country: country || null, code: code || null, displayName: data.display_name || country || '' };
|
|
const body = JSON.stringify(result);
|
|
|
|
if (country && code) {
|
|
ctx.waitUntil(setCachedData(cacheKey, result, 604800));
|
|
}
|
|
|
|
return new Response(body, {
|
|
status: 200,
|
|
headers: {
|
|
...cors,
|
|
'Content-Type': 'application/json',
|
|
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=3600',
|
|
},
|
|
});
|
|
} catch (err) {
|
|
return jsonResponse({ error: 'Nominatim request failed' }, 502, cors);
|
|
}
|
|
}
|