AIS fixes + daily
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import express from 'express';
|
||||
import {resolve, dirname} from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {cfg, localDateStr} from './config.mjs';
|
||||
import {cfg} from './config.mjs';
|
||||
import {queryCurrent, queryHourly, queryDaily, getCoords} from './sensors.mjs';
|
||||
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
|
||||
import {apiReference} from '@scalar/express-api-reference';
|
||||
@@ -10,11 +10,13 @@ import {existsSync} from 'fs';
|
||||
import {getAIS} from './ais.mjs';
|
||||
import {getADSB, getADSBHistory, getADSBRange, initAircraftDb} from './adsb.mjs';
|
||||
import {fetchIcon} from './openweather.mjs';
|
||||
import {lastForecast, getForecast, getWeatherCondition, forecastTTL} from './forecast.mjs';
|
||||
import {getForecast, getWeatherCondition, forecastTTL} from './forecast.mjs';
|
||||
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
||||
|
||||
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
||||
|
||||
const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('[unhandledRejection]', promise, '\nReason:', reason)
|
||||
})
|
||||
@@ -47,7 +49,7 @@ function filterArr(arr, fields) {
|
||||
|
||||
// ── Current ───────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/current', async (req, res) => {
|
||||
app.get('/api/current', asyncHandler(async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const [sensors, coords] = await Promise.all([queryCurrent(), getCoords()])
|
||||
const space = getCelestialCurrent(coords.latitude, coords.longitude)
|
||||
@@ -67,21 +69,19 @@ app.get('/api/current', async (req, res) => {
|
||||
}
|
||||
|
||||
res.json(filterFields(merged, fields))
|
||||
})
|
||||
}));
|
||||
|
||||
// ── Hourly ────────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/hourly', async (req, res) => {
|
||||
app.get('/api/hourly', asyncHandler(async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const coords = await getCoords()
|
||||
const now = new Date()
|
||||
now.setMinutes(0, 0, 0)
|
||||
const forecastLimit = new Date(now.getTime() + 24 * 60 * 60_000)
|
||||
|
||||
const start = req.query.start ? new Date(req.query.start) : now
|
||||
const end = req.query.end ? new Date(req.query.end) : forecastLimit
|
||||
|
||||
const coords = await getCoords()
|
||||
|
||||
const [history, forecast24, forecast3rdParty] = await Promise.all([
|
||||
start.getTime() < now.getTime() ? queryHourly(start, now) : Promise.resolve([]),
|
||||
end.getTime() > now.getTime() ? getForecast().then(f => f.slots.filter(h => new Date(h.time) <= end)) : Promise.resolve([]),
|
||||
@@ -90,55 +90,46 @@ app.get('/api/hourly', async (req, res) => {
|
||||
|
||||
const hourly = getCelestialForecast(coords.latitude, coords.longitude, [...history, ...forecast24, ...forecast3rdParty])
|
||||
res.json(filterArr(hourly, fields))
|
||||
})
|
||||
}));
|
||||
|
||||
// ── Daily ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/daily', async (req, res) => {
|
||||
app.get('/api/daily', asyncHandler(async (req, res) => {
|
||||
const { fields } = req.query
|
||||
const coords = await getCoords();
|
||||
const now = new Date()
|
||||
const todayStart = new Date(now)
|
||||
todayStart.setHours(0, 0, 0, 0)
|
||||
const next = new Date(todayStart)
|
||||
next.setDate(todayStart.getDate() + 1)
|
||||
const todayEnd = new Date(todayStart)
|
||||
todayEnd.setDate(todayStart.getDate() + 1)
|
||||
const start = req.query.start ? new Date(req.query.start) : todayEnd
|
||||
const end = req.query.end ? new Date(req.query.end) : new Date(todayEnd.getTime() + 5 * 24 * 60 * 60_000)
|
||||
|
||||
const start = req.query.start ? new Date(req.query.start) : todayStart
|
||||
const end = req.query.end ? new Date(req.query.end) : next
|
||||
|
||||
const coords = await getCoords()
|
||||
const todayStr = localDateStr(now)
|
||||
const usePhysics = lastForecast.summary && start <= now && end > now
|
||||
|
||||
const [sensorResult] = await Promise.allSettled([start < now ? queryDaily(start, now) : Promise.resolve([])])
|
||||
const history = sensorResult.status === 'fulfilled' ? sensorResult.value : []
|
||||
|
||||
const meteoFrom = new Date(Math.max(next.getTime(), start.getTime()))
|
||||
const [meteoResult] = await Promise.allSettled([end > next ? dailyWeather(coords.latitude, coords.longitude, meteoFrom, end) : Promise.resolve([])])
|
||||
const meteo = meteoResult.status === 'fulfilled' ? meteoResult.value : []
|
||||
|
||||
const todaySlot = usePhysics ? [lastForecast.summary] : []
|
||||
const meteoClean = meteo.filter(r => localDateStr(new Date(r.time)) !== todayStr)
|
||||
|
||||
const daily = getCelestialForecast(coords.latitude, coords.longitude, [...history, ...todaySlot, ...meteoClean])
|
||||
const [history, current, future] = Promise.all([
|
||||
start.getTime() < todayStart.getTime() ? queryDaily(start, todayStart) : Promise.resolve([]), // Historic
|
||||
start.getTime() <= todayStart.getTime() ? queryCurrent().then(resp => [resp]) : Promise.resolve([]), // Current
|
||||
start.getTime() >= todayEnd.getTime() ? dailyWeather(coords.latitude, coords.longitude, todayStart, end) : Promise.resolve([]), // Future
|
||||
]);
|
||||
const daily = [...history, ...current, ...getCelestialForecast(coords.latitude, coords.longitude, future)];
|
||||
res.json(filterArr(daily, fields))
|
||||
})
|
||||
}));
|
||||
|
||||
// ── Position ──────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/position', async (req, res) => {
|
||||
app.get('/api/position', asyncHandler(async (req, res) => {
|
||||
const {fields} = req.query;
|
||||
const data = await getCoords();
|
||||
res.json(filterFields(data, fields));
|
||||
});
|
||||
}));
|
||||
|
||||
// ── Icon ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/icon/:icon', async (req, resp, next) => {
|
||||
app.get('/api/icon/:icon', asyncHandler(async (req, res) => {
|
||||
const { icon } = req.params;
|
||||
if(!icon) return next(); // 404 fallback can handle missing icons
|
||||
resp.contentType('image/png');
|
||||
resp.sendFile(await fetchIcon(icon));
|
||||
});
|
||||
}));
|
||||
|
||||
// ── Space ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -150,20 +141,20 @@ app.get('/api/icon/:icon', async (req, resp, next) => {
|
||||
|
||||
// ── ADSB Proxy ────────────────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/adsb', async (req, res) => res.json(await getADSB()));
|
||||
app.get('/api/adsb/:icao', async (req, res) => res.json(await getADSBHistory(req.params.icao)));
|
||||
app.get('/api/ais', async (req, res) => res.json(await getAIS()));
|
||||
app.get('/api/range', async (req, res) => res.json(await getADSBRange()));
|
||||
app.get('/api/adsb', asyncHandler(async (req, res) => res.json(await getADSB())));
|
||||
app.get('/api/adsb/:icao', asyncHandler(async (req, res) => res.json(await getADSBHistory(req.params.icao))));
|
||||
app.get('/api/ais', asyncHandler(async (req, res) => res.json(await getAIS())));
|
||||
app.get('/api/range', asyncHandler(async (req, res) => res.json(await getADSBRange())));
|
||||
|
||||
// ── DOCS ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
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) => {
|
||||
app.get('/openapi.json',asyncHandler(async (req, res) => res.json(spec)));
|
||||
app.get('/openapi.yaml',asyncHandler(async (req, res) => {
|
||||
const {stringify} = await import('yaml');
|
||||
res.setHeader('Content-Type', 'text/yaml');
|
||||
res.send(stringify(spec));
|
||||
});
|
||||
}));
|
||||
|
||||
// ── Website ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user