fix(basemap): serve CARTO key from backend, bound the map gate, add source attribution

Review follow-up:

- Drop the Next.js route. CARTO_API_KEY is now a regular backend registry
  key (env, .env, or the API Keys panel) served by public
  GET /api/basemap-config. Every frontend mode already proxies /api/* to
  the backend (Next.js proxy in web mode, companion server in packaged
  desktop), so this covers web and desktop with one mechanism and leaves
  the static export untouched. Also removes the invalid non-handler
  export from the route module by removing the module.
- useBasemapConfig: fail open to the unkeyed style after 3 s, abort the
  request at 15 s, apply a late key when it arrives, cache successes per
  page and retry failures on the next mount.
- Declare OSM/CARTO attribution on the raster source (same markup as the
  viewer's existing AttributionControl so MapLibre de-duplicates it).
- Tests: backend endpoint (unset / set+trimmed / persisted operator key /
  registry), hook behaviour (success, non-OK, network error, soft timeout
  then late key, hard abort, shared request and retry), attribution and
  gating source checks.
- CARTO_API_KEY moves to the backend service in docker-compose.yml; docs
  updated accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
C3B2W23
2026-09-13 15:16:47 -07:00
co-authored by Claude Fable 5.1
parent 71550b4adf
commit 667f51cb7a
13 changed files with 306 additions and 96 deletions
+16
View File
@@ -225,6 +225,15 @@ API_REGISTRY = [
"url": "https://dataspace.copernicus.eu/",
"required": False,
},
{
"id": "carto_api_key",
"env_key": "CARTO_API_KEY",
"name": "CARTO Basemaps",
"description": "API key for the CARTO raster basemap behind the DEFAULT dark/light map. CARTO requires one; without it tiles still load but carry an \"API KEY REQUIRED\" watermark. Free at carto.com/basemaps/apikey (no CARTO account needed, 5M tiles/month). Unlike the other keys this one is sent to the browser (GET /api/basemap-config) because the browser passes it to CARTO on every tile request.",
"category": "Imagery",
"url": "https://carto.com/basemaps/apikey",
"required": False,
},
]
ALLOWED_ENV_KEYS = {
@@ -391,6 +400,13 @@ def get_api_keys():
return result
def get_basemap_config() -> dict:
"""Public config for the browser map: the CARTO key (or empty when unset)."""
load_persisted_api_keys_into_environ()
key = os.environ.get("CARTO_API_KEY", "").strip()
return {"carto": {"configured": bool(key), "key": key}}
def save_api_keys(updates: dict[str, str]) -> dict:
"""Persist allowed API keys from a local operator request.