refactor part 1
This commit is contained in:
@@ -1,151 +1,146 @@
|
||||
import express from 'express'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { cfg } from './config.mjs'
|
||||
import { queryCurrent, queryHourly, queryDaily, getCoords } from './influx.mjs'
|
||||
import { getCelestialCurrent, getCelestialHourly, getCelestialDaily } from './celestial.mjs'
|
||||
import { getSpaceWeather } from './space.mjs'
|
||||
import { getOpenMeteo } from './openmeteo.mjs'
|
||||
import { apiReference } from '@scalar/express-api-reference'
|
||||
import { spec } from './spec.mjs'
|
||||
import { existsSync } from 'fs'
|
||||
import express from 'express';
|
||||
import {resolve, dirname} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {cfg} from './config.mjs';
|
||||
import {queryCurrent, queryHourly, queryDaily, getCoords} from './influx.mjs';
|
||||
import {getCelestialCurrent, getCelestialHourly, getCelestialDaily} from './celestial.mjs';
|
||||
import {getSpaceWeather} from './space.mjs';
|
||||
import {getOpenMeteo} from './openmeteo.mjs';
|
||||
import {apiReference} from '@scalar/express-api-reference';
|
||||
import {spec} from './spec.mjs';
|
||||
import {existsSync} from 'fs';
|
||||
import {getAirTraffic, getAirTrafficHistory, getShapes} from './airtraffic.mjs';
|
||||
|
||||
const app = express()
|
||||
const DIR = dirname(fileURLToPath(import.meta.url))
|
||||
const CLIENT_DIST = resolve(DIR, 'public')
|
||||
const app = express();
|
||||
const DIR = dirname(fileURLToPath(import.meta.url));
|
||||
const CLIENT_DIST = resolve(DIR, 'public');
|
||||
|
||||
app.use(express.json())
|
||||
app.use('/icons', express.static(resolve(DIR, 'public', 'icons')))
|
||||
app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); next() })
|
||||
app.use(express.static(CLIENT_DIST))
|
||||
app.use(express.json());
|
||||
app.use('/icons', express.static(resolve(DIR, 'public', 'icons')));
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
next();
|
||||
});
|
||||
app.use(express.static(CLIENT_DIST));
|
||||
|
||||
function filterFields(obj, fields) {
|
||||
if (!fields) return obj
|
||||
const keys = new Set(fields.split(',').map(f => f.trim()))
|
||||
return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.has(k)))
|
||||
if(!fields) return obj;
|
||||
const keys = new Set(fields.split(',').map(f => f.trim()));
|
||||
return Object.fromEntries(Object.entries(obj).filter(([k]) => keys.has(k)));
|
||||
}
|
||||
|
||||
function filterArr(arr, fields) {
|
||||
if (!fields) return arr
|
||||
return arr.map(row => filterFields(row, fields))
|
||||
if(!fields) return arr;
|
||||
return arr.map(row => filterFields(row, fields));
|
||||
}
|
||||
|
||||
function mergeRows(arrays) {
|
||||
const map = {}
|
||||
for (const arr of arrays) {
|
||||
for (const row of arr) {
|
||||
map[row.time] = { ...map[row.time], ...row }
|
||||
}
|
||||
}
|
||||
return Object.values(map).sort((a, b) => a.time.localeCompare(b.time))
|
||||
const map = {};
|
||||
for(const arr of arrays) {
|
||||
for(const row of arr) {
|
||||
map[row.time] = {...map[row.time], ...row};
|
||||
}
|
||||
}
|
||||
return Object.values(map).sort((a, b) => a.time.localeCompare(b.time));
|
||||
}
|
||||
|
||||
// ── GET /api/data ─────────────────────────────────────────────────────────────
|
||||
// ── Sensor Data ───────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/data', async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const coords = await getCoords()
|
||||
app.get('/api/current', async (req, res) => {
|
||||
const {fields} = req.query;
|
||||
const data = await queryCurrent();
|
||||
res.json(filterFields(data, fields));
|
||||
});
|
||||
|
||||
const [sensor, space, meteo] = await Promise.allSettled([
|
||||
queryCurrent(),
|
||||
getSpaceWeather(),
|
||||
getOpenMeteo(coords.lat, coords.lon),
|
||||
])
|
||||
// ── Position ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const celestial = getCelestialCurrent(coords.lat, coords.lon)
|
||||
app.get('/api/position', async (req, res) => {
|
||||
const {fields} = req.query;
|
||||
const data = await getCoords();
|
||||
res.json(filterFields(data, fields));
|
||||
});
|
||||
|
||||
const data = {
|
||||
gps_lat: coords.lat,
|
||||
gps_lon: coords.lon,
|
||||
gps_alt: coords.alt,
|
||||
...(sensor.status === 'fulfilled' ? sensor.value : {}),
|
||||
...celestial,
|
||||
...(space.status === 'fulfilled' ? space.value : {}),
|
||||
...(meteo.status === 'fulfilled' && meteo.value.current ? meteo.value.current : {}),
|
||||
}
|
||||
// ── Space ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
res.json(filterFields(data, fields))
|
||||
})
|
||||
app.get('/api/space', async (req, res) => {
|
||||
const {fields, mode} = req.query;
|
||||
const start = req.query.start || new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
|
||||
const end = req.query.end || new Date().toISOString();
|
||||
const coords = await getCoords();
|
||||
if(mode === 'hourly') {
|
||||
const space = await getSpaceWeather();
|
||||
Object.assign(space, getCelestialHourly(coords.lat, coords.lon, start, end));
|
||||
res.json(filterFields(space, fields));
|
||||
} else if(mode === 'daily') {
|
||||
const space = await getSpaceWeather();
|
||||
Object.assign(space, getCelestialDaily(coords.lat, coords.lon, start, end));
|
||||
res.json(filterFields(space, fields));
|
||||
} else {
|
||||
const space = await getSpaceWeather();
|
||||
Object.assign(space, getCelestialCurrent(coords.lat, coords.lon));
|
||||
res.json(filterFields(space, fields));
|
||||
}
|
||||
});
|
||||
|
||||
// ── Daily History/Forecast ────────────────────────────────────────────────────
|
||||
|
||||
// ── GET /api/hourly ───────────────────────────────────────────────────────────
|
||||
app.get('/api/hourly', async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const start = req.query.start || new Date(new Date().setHours(0,0,0,0)).toISOString()
|
||||
const end = req.query.end || new Date().toISOString()
|
||||
const coords = await getCoords()
|
||||
const {fields} = req.query;
|
||||
const start = req.query.start || new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const end = req.query.end || new Date().toISOString();
|
||||
const coords = await getCoords();
|
||||
const [sensor, meteo] = await Promise.allSettled([
|
||||
queryHourly(start, now),
|
||||
getOpenMeteo(coords.lat, coords.lon, now, end),
|
||||
]);
|
||||
const history = sensor.status === 'fulfilled' ? sensor.value : [];
|
||||
const forecast = meteo.status === 'fulfilled' ? meteo.value.hourly : [];
|
||||
res.json(filterArr([...history, ...forecast], fields));
|
||||
});
|
||||
|
||||
const [sensor, meteo, space] = await Promise.allSettled([
|
||||
queryHourly(start, end),
|
||||
getOpenMeteo(coords.lat, coords.lon, start, end),
|
||||
getSpaceWeather(),
|
||||
])
|
||||
|
||||
const sensorRows = sensor.status === 'fulfilled' ? sensor.value : []
|
||||
const meteoHourly = meteo.status === 'fulfilled' ? meteo.value.hourly : []
|
||||
const spaceData = space.status === 'fulfilled' ? space.value : {}
|
||||
const seedRows = sensorRows.length ? sensorRows : meteoHourly
|
||||
const celestial = getCelestialHourly(coords.lat, coords.lon, seedRows)
|
||||
|
||||
// Spread space weather into every hourly row
|
||||
const result = mergeRows([sensorRows, meteoHourly, celestial])
|
||||
.map(row => ({ gps_lat: coords.lat, gps_lon: coords.lon, gps_alt: coords.alt, ...spaceData, ...row }))
|
||||
|
||||
res.json(filterArr(result, fields))
|
||||
})
|
||||
|
||||
// ── GET /api/daily ------------------------------------------------------------
|
||||
// ── Hourly History/Forecast ───────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/daily', async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const start = req.query.start || new Date(new Date().setHours(0,0,0,0)).toISOString()
|
||||
const end = req.query.end || new Date().toISOString()
|
||||
const coords = await getCoords()
|
||||
const {fields} = req.query;
|
||||
const start = req.query.start || new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
|
||||
const now = new Date().toISOString();
|
||||
const end = req.query.end || new Date().toISOString();
|
||||
const coords = await getCoords();
|
||||
const [sensor, meteo] = await Promise.allSettled([
|
||||
queryDaily(start, now),
|
||||
getOpenMeteo(coords.lat, coords.lon, now, end),
|
||||
]);
|
||||
const history = sensor.status === 'fulfilled' ? sensor.value : [];
|
||||
const forecast = meteo.status === 'fulfilled' ? meteo.value.daily : [];
|
||||
res.json(filterArr([...history, ...forecast], fields));
|
||||
});
|
||||
|
||||
const [sensor, meteo, space] = await Promise.allSettled([
|
||||
queryDaily(start, end),
|
||||
getOpenMeteo(coords.lat, coords.lon, start, end),
|
||||
getSpaceWeather(),
|
||||
])
|
||||
// ── ADSB Proxy ────────────────────────────────────────────────────────────────
|
||||
|
||||
const sensorRows = sensor.status === 'fulfilled' ? sensor.value : []
|
||||
const meteoDaily = meteo.status === 'fulfilled' ? meteo.value.daily : []
|
||||
const spaceData = space.status === 'fulfilled' ? space.value : {}
|
||||
const seedRows = sensorRows.length ? sensorRows : meteoDaily
|
||||
const celestial = getCelestialDaily(coords.lat, coords.lon, seedRows)
|
||||
app.get('/api/air-traffic', async (req, res) => res.json(await getAirTraffic()));
|
||||
app.get('/api/air-traffic/:icao', async (req, res) => res.json(await getAirTrafficHistory(req.params.icao)));
|
||||
app.get('/api/air-traffic-shapes', async (req, res) => res.json(await getShapes()));
|
||||
|
||||
const result = mergeRows([sensorRows, meteoDaily, celestial])
|
||||
.map(row => ({ location: coords,gps_lat: coords.lat, gps_lon: coords.lon, gps_alt: coords.alt, ...spaceData, ...row }))
|
||||
// ── DOCS ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
res.json(filterArr(result, fields))
|
||||
})
|
||||
|
||||
// -- ADSB ----------------------------------------------------------------------
|
||||
app.get('/api/air-traffic', async (req, res) => res.json(await getAirTraffic()))
|
||||
app.get('/api/air-traffic/:icao', async (req, res) => res.json(await getAirTrafficHistory(req.params.icao)))
|
||||
app.get('/api/air-traffic-shapes', async (req, res) => res.json(await getShapes()))
|
||||
|
||||
// ── DOCS ----------------------------------------------------------------------
|
||||
|
||||
app.get('/openapi.json', (req, res) => res.json(spec))
|
||||
app.use('/docs', apiReference({spec: {url: '/openapi.json'}, theme: 'default'}));
|
||||
app.get('/openapi.json', (req, res) => res.json(spec));
|
||||
app.get('/openapi.yaml', async (req, res) => {
|
||||
const { stringify } = await import('yaml')
|
||||
res.setHeader('Content-Type', 'text/yaml')
|
||||
res.send(stringify(spec))
|
||||
})
|
||||
const {stringify} = await import('yaml');
|
||||
res.setHeader('Content-Type', 'text/yaml');
|
||||
res.send(stringify(spec));
|
||||
});
|
||||
|
||||
// Scalar UI
|
||||
app.use('/docs', apiReference({ spec: { url: '/openapi.json' }, theme: 'default' }))
|
||||
// ── Website ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
// ── SPA Redirect --------------------------------------------------------------
|
||||
app.get('*', (req, res) => {
|
||||
const index = resolve(CLIENT_DIST, 'index.html')
|
||||
if (existsSync(index)) res.sendFile(index)
|
||||
else res.status(404).send('Client not built yet — run npm run build in /client')
|
||||
})
|
||||
app.get('*', (req, res) => {
|
||||
const index = resolve(CLIENT_DIST, 'index.html');
|
||||
if(existsSync(index)) res.sendFile(index);
|
||||
else res.status(404).send('Client not built yet — run npm run build in /client');
|
||||
});
|
||||
|
||||
// ── Start ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
const c = cfg()
|
||||
app.listen(c.PORT, () => console.log(`🌦 Weather API — http://localhost:${c.PORT}`))
|
||||
const c = cfg();
|
||||
app.listen(c.PORT, () => console.log(`🌦 Weather API — http://localhost:${c.PORT}`));
|
||||
|
||||
Reference in New Issue
Block a user