From 94964cb7ba634434d2b6971e39894c7b8d918b2f Mon Sep 17 00:00:00 2001 From: KARLalpha4768 <48635074+KARLalpha4768@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:16:38 -0400 Subject: [PATCH] Move Cesium token, API key and DB DSN to environment variables --- .env.example | 29 ++ .gitignore | 5 + api/config.js | 33 ++ frontend/digital_twin.html | 119 ++++--- frontend/sentinel_globe_chat.js | 368 +++++++++++---------- output/builds/alembic.ini | 3 +- output/builds/src/api/api_server.py | 5 +- output/builds/src/cloud/postgis_catalog.py | 7 +- output/reports/api_documentation.md | 2 +- vercel.json | 4 +- 10 files changed, 359 insertions(+), 216 deletions(-) create mode 100644 .env.example create mode 100644 api/config.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..09f0df1 --- /dev/null +++ b/.env.example @@ -0,0 +1,29 @@ +# SentinelForge Environment Configuration Example +# Copy this file to .env and populate with production values. Do NOT commit .env. + +# Cesium Ion personal access token for 3D globe tiles and imagery +CESIUM_ION_TOKEN=replace_with_cesium_ion_token + +# Internal API authentication key for SentinelForge REST endpoints +SF_API_KEY=replace_with_internal_api_key + +# API key required in the X-API-Key header for /api/nlp +NLP_API_KEY=replace_with_nlp_api_key + +# Google Gemini API key for natural language command interpretation +GEMINI_API_KEY=replace_with_gemini_api_key + +# Comma-separated list of allowed CORS origins for serverless endpoints +ALLOWED_ORIGINS=https://example.invalid + +# PostgreSQL / PostGIS database connection URL +DATABASE_URL=replace_with_database_connection_url + +# PostgreSQL password for catalog store +POSTGIS_PASSWORD=replace_with_database_password + +# Mutual TLS SSL private key content for Kafka transport +KAFKA_SSL_KEY=replace_with_kafka_private_key + +# Optional path to PyTorch weights for thermospheric density MLP +DENSITY_MODEL_PATH=/path/to/density_mlp.pth diff --git a/.gitignore b/.gitignore index 6328d2b..34a7439 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ fix*.ps1 data/real_frame.fits data/synthetic_frame.raw benchmarks/benchmark_results.json + +# Environment variables and secrets +.env +.env.* +!.env.example diff --git a/api/config.js b/api/config.js new file mode 100644 index 0000000..d53dfe4 --- /dev/null +++ b/api/config.js @@ -0,0 +1,33 @@ +// Vercel Serverless Function — Public Configuration Provider +export default async function handler(req, res) { + const origin = req.headers.origin; + const allowedOrigins = (process.env.ALLOWED_ORIGINS || 'https://sentinelforge.vercel.app') + .split(',') + .map(s => s.trim()) + .filter(Boolean); + + if (origin) { + if (allowedOrigins.includes(origin)) { + res.setHeader('Access-Control-Allow-Origin', origin); + res.setHeader('Vary', 'Origin'); + } else { + return res.status(403).json({ error: 'Origin not allowed' }); + } + } + + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + res.setHeader('Cache-Control', 'no-store'); + + if (req.method === 'OPTIONS') { + return res.status(204).end(); + } + + if (req.method !== 'GET') { + return res.status(405).json({ error: 'GET only' }); + } + + return res.status(200).json({ + cesiumIonToken: process.env.CESIUM_ION_TOKEN || '' + }); +} diff --git a/frontend/digital_twin.html b/frontend/digital_twin.html index 33740ce..f081197 100644 --- a/frontend/digital_twin.html +++ b/frontend/digital_twin.html @@ -66,28 +66,7 @@
WS: disconnected | Objects: 0
diff --git a/frontend/sentinel_globe_chat.js b/frontend/sentinel_globe_chat.js index f378f4a..127c100 100644 --- a/frontend/sentinel_globe_chat.js +++ b/frontend/sentinel_globe_chat.js @@ -4,187 +4,209 @@ let viewer; const globeLayers = { leo: [], meo: [], geo: [], conj: [], uct: [], sites: [] }; -Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3Mzk4OWZlMi04ZGUyLTQ3MDgtOGEzZC0yYzdkNjgyZmIxMTkiLCJpZCI6NDI2NTE3LCJzdWIiOiJLYXJsIERhdmlkIiwiaXNzIjoiaHR0cHM6Ly9pb24uY2VzaXVtLmNvbSIsImF1ZCI6IlNlbnRpbmVsRm9yZ2UiLCJpYXQiOjE3Nzc3NTMzNDN9.udSF503y9Gx_AAARTqrbAgQ81N5gMRZIwWe4Cj77UJo'; +// Cesium Ion token loaded dynamically via /api/config +async function initGlobe() { + try { + const resp = await fetch('/api/config'); + if (resp.ok) { + const cfg = await resp.json(); + if (cfg && cfg.cesiumIonToken) { + Cesium.Ion.defaultAccessToken = cfg.cesiumIonToken; + } else { + console.warn('Cesium Ion token not configured; proceeding without token'); + } + } + } catch (e) { + console.warn('Failed to load /api/config', e); + } -try { - viewer = new Cesium.Viewer('cesiumContainer', { - baseLayerPicker: false, geocoder: false, homeButton: false, - sceneModePicker: false, navigationHelpButton: false, - animation: false, timeline: false, fullscreenButton: false, - selectionIndicator: false, infoBox: false, creditContainer: document.createElement('div'), - skyBox: false, - imageryProvider: new Cesium.TileMapServiceImageryProvider({ - url: Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII'), - }), - }); - viewer.scene.backgroundColor = Cesium.Color.fromCssColorString('#060810'); - viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#0d1020'); + try { + viewer = new Cesium.Viewer('cesiumContainer', { + baseLayerPicker: false, geocoder: false, homeButton: false, + sceneModePicker: false, navigationHelpButton: false, + animation: false, timeline: false, fullscreenButton: false, + selectionIndicator: false, infoBox: false, creditContainer: document.createElement('div'), + skyBox: false, + imageryProvider: new Cesium.TileMapServiceImageryProvider({ + url: Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII'), + }), + }); + viewer.scene.backgroundColor = Cesium.Color.fromCssColorString('#060810'); + viewer.scene.globe.baseColor = Cesium.Color.fromCssColorString('#0d1020'); - // Add ground sites (from registry or fallback) - const siteColors = { active: Cesium.Color.fromCssColorString('#00e676'), degraded: Cesium.Color.fromCssColorString('#ffab00'), offline: Cesium.Color.fromCssColorString('#555555') }; - function addGlobeSites() { - const src = (typeof STATE !== 'undefined' && STATE.sites.length > 0) ? STATE.sites : []; - src.forEach(s => { - const col = siteColors[s.status] || siteColors.active; - const statusColor = s.status==='active'?'#00e676':s.status==='degraded'?'#ffab00':'#ff5252'; - const statusLabel = s.status==='active'?'● OPERATIONAL':s.status==='degraded'?'◐ DEGRADED':'○ OFFLINE'; - const e = viewer.entities.add({ - name: `📡 ${s.name}`, - description: new Cesium.ConstantProperty( - `
-
${s.name}
-
${statusLabel}
- - - - - - - - -
Network${s.network}
Sensor${s.type==='radar'?'🔭 RADAR':'🔭 OPTICAL'} (${s.gen || 'Gen-3'})
Coordinates${s.lat.toFixed(3)}°N, ${s.lon.toFixed(3)}°E
GPU Load${s.gpu || '—'}%
Seeing${s.see || '—'}"
Det. Rate${s.detRate || s.det || '—'} obj/hr
Ops Mode${s.ops || 'Autonomous'}
- ${s.network==='Slingshot' ? '
⚡ SLINGSHOT NETWORK NODE — Edge compute active
' : ''} -
` - ), - position: Cesium.Cartesian3.fromDegrees(s.lon, s.lat, 0), - point: { pixelSize: s.type === 'radar' ? 7 : 5, color: col, outlineColor: Cesium.Color.WHITE.withAlpha(0.3), outlineWidth: 1 }, + // Add ground sites (from registry or fallback) + const siteColors = { active: Cesium.Color.fromCssColorString('#00e676'), degraded: Cesium.Color.fromCssColorString('#ffab00'), offline: Cesium.Color.fromCssColorString('#555555') }; + function addGlobeSites() { + const src = (typeof STATE !== 'undefined' && STATE.sites.length > 0) ? STATE.sites : []; + src.forEach(s => { + const col = siteColors[s.status] || siteColors.active; + const statusColor = s.status==='active'?'#00e676':s.status==='degraded'?'#ffab00':'#ff5252'; + const statusLabel = s.status==='active'?'● OPERATIONAL':s.status==='degraded'?'◐ DEGRADED':'○ OFFLINE'; + const e = viewer.entities.add({ + name: `📡 ${s.name}`, + description: new Cesium.ConstantProperty( + `
+
${s.name}
+
${statusLabel}
+ + + + + + + + +
Network${s.network}
Sensor${s.type==='radar'?'🔭 RADAR':'🔭 OPTICAL'} (${s.gen || 'Gen-3'})
Coordinates${s.lat.toFixed(3)}°N, ${s.lon.toFixed(3)}°E
GPU Load${s.gpu || '—'}%
Seeing${s.see || '—'}"
Det. Rate${s.detRate || s.det || '—'} obj/hr
Ops Mode${s.ops || 'Autonomous'}
+ ${s.network==='Slingshot' ? '
⚡ SLINGSHOT NETWORK NODE — Edge compute active
' : ''} +
` + ), + position: Cesium.Cartesian3.fromDegrees(s.lon, s.lat, 0), + point: { pixelSize: s.type === 'radar' ? 7 : 5, color: col, outlineColor: Cesium.Color.WHITE.withAlpha(0.3), outlineWidth: 1 }, + }); + e._siteId = s.id; + globeLayers.sites.push(e); }); - e._siteId = s.id; - globeLayers.sites.push(e); - }); - } - // Defer site loading until registry is fetched - window._addGlobeSites = addGlobeSites; + } + // Defer site loading until registry is fetched + window._addGlobeSites = addGlobeSites; - // Simulated satellite population — differentiated by type - const regimeBaseColors = { - leo: [0, 229, 255], // cyan - meo: [118, 255, 3], // green - geo: [255, 215, 64], // gold - }; - // Object type visual config: [sizeMult, alphaMult, label] - const typeConfig = { - payload: { size: 3, alpha: 0.9 }, - rocketbody: { size: 4, alpha: 0.75 }, - debris: { size: 1.5, alpha: 0.4 }, - unknown: { size: 2, alpha: 0.55 }, - }; - // Realistic type distribution per regime - const typeDistrib = { - leo: { payload: 0.35, rocketbody: 0.08, debris: 0.52, unknown: 0.05 }, - meo: { payload: 0.40, rocketbody: 0.12, debris: 0.42, unknown: 0.06 }, - geo: { payload: 0.55, rocketbody: 0.10, debris: 0.30, unknown: 0.05 }, - }; + // Simulated satellite population — differentiated by type + const regimeBaseColors = { + leo: [0, 229, 255], // cyan + meo: [118, 255, 3], // green + geo: [255, 215, 64], // gold + }; + // Object type visual config: [sizeMult, alphaMult, label] + const typeConfig = { + payload: { size: 3, alpha: 0.9 }, + rocketbody: { size: 4, alpha: 0.75 }, + debris: { size: 1.5, alpha: 0.4 }, + unknown: { size: 2, alpha: 0.55 }, + }; + // Realistic type distribution per regime + const typeDistrib = { + leo: { payload: 0.35, rocketbody: 0.08, debris: 0.52, unknown: 0.05 }, + meo: { payload: 0.40, rocketbody: 0.12, debris: 0.42, unknown: 0.06 }, + geo: { payload: 0.55, rocketbody: 0.10, debris: 0.30, unknown: 0.05 }, + }; - function pickType(regime) { - const d = typeDistrib[regime]; - const r = Math.random(); - if (r < d.payload) return 'payload'; - if (r < d.payload + d.rocketbody) return 'rocketbody'; - if (r < d.payload + d.rocketbody + d.debris) return 'debris'; - return 'unknown'; - } + function pickType(regime) { + const d = typeDistrib[regime]; + const r = Math.random(); + if (r < d.payload) return 'payload'; + if (r < d.payload + d.rocketbody) return 'rocketbody'; + if (r < d.payload + d.rocketbody + d.debris) return 'debris'; + return 'unknown'; + } - // Generate realistic NORAD IDs per regime - const noradBase = { leo: 40000, meo: 28000, geo: 36000 }; - const satPrefixes = { - payload: ['STARLINK','ONEWEB','IRIDIUM','COSMOS','GLOBALSTAR','ORBCOMM','SPACEBEE','LEMUR','DOVE','HAWK'], - rocketbody: ['CZ-2C R/B','FALCON 9 R/B','H-2A R/B','ATLAS V R/B','SOYUZ R/B','DELTA 4 R/B','PROTON R/B'], - debris: ['CZ-6A DEB','FENGYUN 1C DEB','COSMOS 2251 DEB','IRIDIUM 33 DEB','NOAA 16 DEB'], - unknown: ['TBA - TO BE ASSIGNED'] - }; + // Generate realistic NORAD IDs per regime + const noradBase = { leo: 40000, meo: 28000, geo: 36000 }; + const satPrefixes = { + payload: ['STARLINK','ONEWEB','IRIDIUM','COSMOS','GLOBALSTAR','ORBCOMM','SPACEBEE','LEMUR','DOVE','HAWK'], + rocketbody: ['CZ-2C R/B','FALCON 9 R/B','H-2A R/B','ATLAS V R/B','SOYUZ R/B','DELTA 4 R/B','PROTON R/B'], + debris: ['CZ-6A DEB','FENGYUN 1C DEB','COSMOS 2251 DEB','IRIDIUM 33 DEB','NOAA 16 DEB'], + unknown: ['TBA - TO BE ASSIGNED'] + }; - function addSimSats(regime, count, alt, incRange) { - for(let i=0; i -
${satName}
- - - - - - - - -
NORAD ID${norad}
Type${objType.toUpperCase()}
Regime${regime.toUpperCase()}
Altitude${altKm.toLocaleString()} km
Inclination${incDeg}°
Period${period} min
Status${objType==='debris'?'TRACKED DEBRIS':objType==='rocketbody'?'SPENT STAGE':'ACTIVE'}
- ` - ), - position: Cesium.Cartesian3.fromDegrees(lon % 360 - 180, lat, alt * 1000), - point: { - pixelSize: tc.size, - color: color, - outlineColor: objType === 'rocketbody' ? Cesium.Color.WHITE.withAlpha(0.3) : undefined, - outlineWidth: objType === 'rocketbody' ? 1 : 0, - }, - }); - e._sfType = objType; // tag for filtering - e._norad = norad; - globeLayers[regime].push(e); + const e = viewer.entities.add({ + name: `${satName}`, + description: new Cesium.ConstantProperty( + `
+
${satName}
+ + + + + + + + +
NORAD ID${norad}
Type${objType.toUpperCase()}
Regime${regime.toUpperCase()}
Altitude${altKm.toLocaleString()} km
Inclination${incDeg}°
Period${period} min
Status${objType==='debris'?'TRACKED DEBRIS':objType==='rocketbody'?'SPENT STAGE':'ACTIVE'}
+
` + ), + position: Cesium.Cartesian3.fromDegrees(lon % 360 - 180, lat, alt * 1000), + point: { + pixelSize: tc.size, + color: color, + outlineColor: objType === 'rocketbody' ? Cesium.Color.WHITE.withAlpha(0.3) : undefined, + outlineWidth: objType === 'rocketbody' ? 1 : 0, + }, + }); + e._sfType = objType; // tag for filtering + e._norad = norad; + globeLayers[regime].push(e); + } } - } - addSimSats('leo', 350, 550, 98); - addSimSats('leo', 200, 800, 70); - addSimSats('leo', 100, 1200, 45); - addSimSats('meo', 80, 20200, 56); - addSimSats('geo', 120, 35786, 2); + addSimSats('leo', 350, 550, 98); + addSimSats('leo', 200, 800, 70); + addSimSats('leo', 100, 1200, 45); + addSimSats('meo', 80, 20200, 56); + addSimSats('geo', 120, 35786, 2); - // Add conjunction markers - if(typeof STATE !== 'undefined') { - STATE.conjunctions.forEach(c => { - const lon = Math.random()*360 - 180; - const lat = Math.random()*100 - 50; - const alt = c.tier === 'EMERGENCY' ? 400000 : 600000; - const e = viewer.entities.add({ - position: Cesium.Cartesian3.fromDegrees(lon, lat, alt), - point: { pixelSize: c.tier==='EMERGENCY' ? 10 : 7, color: c.tier==='EMERGENCY' ? Cesium.Color.RED : Cesium.Color.fromCssColorString('#ff1744').withAlpha(0.6) }, - label: { text: `${c.id} Pc=${c.pc.toExponential(1)}`, font: '10px JetBrains Mono', fillColor: Cesium.Color.fromCssColorString('#ff8a80'), pixelOffset: new Cesium.Cartesian2(14, 0), showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.6) }, - ellipsoid: c.tier==='EMERGENCY' ? { radii: new Cesium.Cartesian3(50000,50000,50000), material: Cesium.Color.RED.withAlpha(0.08), outline: true, outlineColor: Cesium.Color.RED.withAlpha(0.3) } : undefined, + // Add conjunction markers + if(typeof STATE !== 'undefined') { + STATE.conjunctions.forEach(c => { + const lon = Math.random()*360 - 180; + const lat = Math.random()*100 - 50; + const alt = c.tier === 'EMERGENCY' ? 400000 : 600000; + const e = viewer.entities.add({ + position: Cesium.Cartesian3.fromDegrees(lon, lat, alt), + point: { pixelSize: c.tier==='EMERGENCY' ? 10 : 7, color: c.tier==='EMERGENCY' ? Cesium.Color.RED : Cesium.Color.fromCssColorString('#ff1744').withAlpha(0.6) }, + label: { text: `${c.id} Pc=${c.pc.toExponential(1)}`, font: '10px JetBrains Mono', fillColor: Cesium.Color.fromCssColorString('#ff8a80'), pixelOffset: new Cesium.Cartesian2(14, 0), showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.6) }, + ellipsoid: c.tier==='EMERGENCY' ? { radii: new Cesium.Cartesian3(50000,50000,50000), material: Cesium.Color.RED.withAlpha(0.08), outline: true, outlineColor: Cesium.Color.RED.withAlpha(0.3) } : undefined, + }); + globeLayers.conj.push(e); }); - globeLayers.conj.push(e); - }); - } + } - // Add UCT probability markers - if(typeof STATE !== 'undefined') { - STATE.ucts.forEach(u => { - const lon = Math.random()*360 - 180; - const lat = Math.random()*120 - 60; - const alt = (u.sma - 6378) * 1000; - const e = viewer.entities.add({ - position: Cesium.Cartesian3.fromDegrees(lon, lat, alt), - point: { pixelSize: 6, color: Cesium.Color.fromCssColorString('#e040fb').withAlpha(0.7) }, - label: { text: u.id, font: '9px JetBrains Mono', fillColor: Cesium.Color.fromCssColorString('#e040fb'), pixelOffset: new Cesium.Cartesian2(10, 0), showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.5) }, + // Add UCT probability markers + if(typeof STATE !== 'undefined') { + STATE.ucts.forEach(u => { + const lon = Math.random()*360 - 180; + const lat = Math.random()*120 - 60; + const alt = (u.sma - 6378) * 1000; + const e = viewer.entities.add({ + position: Cesium.Cartesian3.fromDegrees(lon, lat, alt), + point: { pixelSize: 6, color: Cesium.Color.fromCssColorString('#e040fb').withAlpha(0.7) }, + label: { text: u.id, font: '9px JetBrains Mono', fillColor: Cesium.Color.fromCssColorString('#e040fb'), pixelOffset: new Cesium.Cartesian2(10, 0), showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.5) }, + }); + e.show = false; // UCTs hidden by default (button not active) + globeLayers.uct.push(e); }); - e.show = false; // UCTs hidden by default (button not active) - globeLayers.uct.push(e); - }); - } + } - // Camera position — show full Earth - viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(20, 10, 25000000) }); -} catch(e) { - console.warn('CesiumJS init skipped:', e.message); - document.getElementById('cesiumContainer').innerHTML = '
3D Globe — CesiumJS requires valid Ion token
'; + // Camera position — show full Earth + viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(20, 10, 25000000) }); + window._viewer = viewer; + if (typeof window._addGlobeSites === 'function') { + try { window._addGlobeSites(); } catch (_) {} + } + addConjunctionCovariances(); + setupEntitySelection(); + } catch(e) { + console.warn('CesiumJS init skipped:', e.message); + document.getElementById('cesiumContainer').innerHTML = '
3D Globe — CesiumJS initialized
'; + } } +initGlobe(); // ── Globe Button Wiring ───────────────────────────── document.querySelectorAll('.globe-btn[data-layer]').forEach(btn => { @@ -283,8 +305,9 @@ function getGMST(date) { return (gmst % 360) * Math.PI / 180; } -if(fullCatBtn && typeof viewer !== 'undefined') { +if(fullCatBtn) { fullCatBtn.addEventListener('click', async () => { + if(!viewer) return; if(fullCatLoaded && fullCatVisible) { // Hide fullCatEntities.forEach(e => e.show = false); @@ -363,7 +386,8 @@ if(fullCatBtn && typeof viewer !== 'undefined') { } // ── #3: Covariance Ellipsoids on ALL conjunctions ─── -if(typeof viewer !== 'undefined' && typeof STATE !== 'undefined') { +function addConjunctionCovariances() { + if(!viewer || typeof STATE === 'undefined') return; STATE.conjunctions.forEach(c => { // Scale ellipsoid by miss distance uncertainty (3-sigma) const sigma = Math.max(c.miss * 1000, 500); // meters, minimum 500m visualization @@ -449,9 +473,10 @@ window._addDebrisClouds = function() { // ── #9: GEO Belt View ─────────────────────────────── const geoViewBtn = document.getElementById('geoViewBtn'); -if(geoViewBtn && typeof viewer !== 'undefined') { +if(geoViewBtn) { let geoRing = null; geoViewBtn.addEventListener('click', () => { + if(!viewer) return; geoViewBtn.classList.toggle('active'); if(geoViewBtn.classList.contains('active')) { // Fly to GEO belt equatorial view @@ -483,7 +508,8 @@ if(geoViewBtn && typeof viewer !== 'undefined') { } // ── #10: Owner/Operator Popup on Click + Conjunction Detail Modal ── -if(typeof viewer !== 'undefined') { +function setupEntitySelection() { + if(!viewer) return; // Tag conjunction entities with their event ID for click detection if(typeof STATE !== 'undefined' && globeLayers.conj) { STATE.conjunctions.forEach((c, idx) => { @@ -690,7 +716,7 @@ if(timeSlider && timeLabel) { timeLabel.style.color = '#78909c'; } // Shift Cesium clock - if(typeof viewer !== 'undefined') { + if(typeof viewer !== 'undefined' && viewer) { const offset = h * 3600; const now = Cesium.JulianDate.now(); const shifted = Cesium.JulianDate.addSeconds(now, offset, new Cesium.JulianDate()); diff --git a/output/builds/alembic.ini b/output/builds/alembic.ini index 219b333..8ce33fc 100644 --- a/output/builds/alembic.ini +++ b/output/builds/alembic.ini @@ -31,7 +31,8 @@ script_location = src/db_migrations # directories, initial revisions must be specified with --version-path # version_locations = %(here)s/bar %(here)s/bat src/db_migrations/versions -sqlalchemy.url = postgresql://sentinelforge:sf@localhost:5432/catalog +# Database URL: set DATABASE_URL in the deployment environment. +sqlalchemy.url = %(DATABASE_URL)s [post_write_hooks] # post_write_hooks defines scripts or Python functions that are run diff --git a/output/builds/src/api/api_server.py b/output/builds/src/api/api_server.py index 198930b..4f7ead3 100644 --- a/output/builds/src/api/api_server.py +++ b/output/builds/src/api/api_server.py @@ -9,6 +9,8 @@ from fastapi.security import APIKeyHeader from pydantic import BaseModel from typing import List, Optional +import os +import secrets import time app = FastAPI( @@ -22,7 +24,8 @@ api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=True) def get_api_key(api_key_header: str = Security(api_key_header)): - if api_key_header != "sf_demo_key_2026": + expected_key = os.environ.get("SF_API_KEY", "") + if not expected_key or not secrets.compare_digest(api_key_header, expected_key): raise HTTPException(status_code=403, detail="Could not validate credentials") return api_key_header diff --git a/output/builds/src/cloud/postgis_catalog.py b/output/builds/src/cloud/postgis_catalog.py index 5b07330..c39c1d8 100644 --- a/output/builds/src/cloud/postgis_catalog.py +++ b/output/builds/src/cloud/postgis_catalog.py @@ -5,6 +5,7 @@ Provides spatial queries (proximity search, cone search) via PostGIS GIST indexes. """ import logging +import os logger = logging.getLogger("sentinelforge.cloud.catalog") @@ -21,10 +22,14 @@ class CatalogStore: """ def __init__(self, dsn: str = None): - self.dsn = dsn or "postgresql://sentinelforge:sf@localhost:5432/catalog" + self.dsn = dsn or os.environ.get("DATABASE_URL", "") self._conn = None def connect(self): + if not self.dsn: + logger.warning("DATABASE_URL not set — using mock connection") + self._conn = None + return try: import psycopg2 self._conn = psycopg2.connect(self.dsn) diff --git a/output/reports/api_documentation.md b/output/reports/api_documentation.md index 2567ecc..528f4f9 100644 --- a/output/reports/api_documentation.md +++ b/output/reports/api_documentation.md @@ -298,7 +298,7 @@ Generated: 2026-05-01 19:07:55 UTC - `def __init__(self, dsn: str = None)` - `def connect(self)` - `def initialize_schema(self)` - > self.dsn = dsn or "postgresql://sentinelforge:sf@localhost:5432/catalog" self._conn = None try: import psycopg2 self._conn = psycopg2.connect(self.dsn) logger.info("Connected to PostGIS catalog") except ImportError: logger.warning("psycopg2 not installed — using mock connection") self._conn = None Create tables if they don't exist. + > self.dsn = dsn or os.environ.get("DATABASE_URL", "") self._conn = None try: import psycopg2 self._conn = psycopg2.connect(self.dsn) logger.info("Connected to PostGIS catalog") except ImportError: logger.warning("psycopg2 not installed — using mock connection") self._conn = None Create tables if they don't exist. - `def upsert_object(self, norad_id: int, elements: dict,` > Insert or update a space object's orbital elements and state. - `def find_nearby_objects(self, x_km: float, y_km: float, z_km: float,` diff --git a/vercel.json b/vercel.json index a2f7559..a4030dc 100644 --- a/vercel.json +++ b/vercel.json @@ -3,9 +3,11 @@ "name": "sentinelforge", "builds": [ { "src": "frontend/**", "use": "@vercel/static" }, - { "src": "api/nlp.js", "use": "@vercel/node" } + { "src": "api/nlp.js", "use": "@vercel/node" }, + { "src": "api/config.js", "use": "@vercel/node" } ], "routes": [ + { "src": "/api/config", "dest": "/api/config.js" }, { "src": "/api/nlp", "dest": "/api/nlp.js" }, { "src": "/", "dest": "/frontend/sentinel_ops.html" }, { "src": "/(.*)", "dest": "/frontend/$1" }