← back to Ventura Bus 3d
src/server.js
117 lines
const path = require('path');
const fs = require('fs');
const express = require('express');
const compression = require('compression');
const morgan = require('morgan');
// Hand-rolled .env loader so we don't pull dotenv just for one key.
const envPath = path.join(__dirname, '..', '.env');
if (fs.existsSync(envPath)) {
for (const line of fs.readFileSync(envPath, 'utf8').split('\n')) {
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
}
}
const app = express();
const PORT = process.env.PORT || 9783;
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
const GMAPS_KEY = process.env.GOOGLE_MAPS_API_KEY || '';
app.use(compression());
app.use(morgan('tiny'));
app.use(express.static(PUBLIC_DIR, {
maxAge: '1h',
setHeaders: (res, filePath) => {
if (filePath.endsWith('.glb') || filePath.endsWith('.gltf') || filePath.endsWith('.ktx2')) {
res.setHeader('Cache-Control', 'public, max-age=86400, immutable');
}
}
}));
app.get('/healthz', (_req, res) => {
res.json({ ok: true, service: 'ventura-bus-3d', time: new Date().toISOString() });
});
const ROUTES = [
{
id: 'metro-150',
line: '150',
name: 'Metro 150 — Ventura Local',
color: '#ff8c00',
laneZ: -2,
stops: [
{ id: 'woodland-hills', label: 'Woodland Hills', lng: -118.6050, lat: 34.1683 },
{ id: 'tarzana', label: 'Tarzana', lng: -118.5530, lat: 34.1700 },
{ id: 'encino', label: 'Encino', lng: -118.5010, lat: 34.1590 },
{ id: 'sherman-oaks', label: 'Sherman Oaks', lng: -118.4490, lat: 34.1510 },
{ id: 'studio-city', label: 'Studio City', lng: -118.3960, lat: 34.1430 }
]
},
{
id: 'metro-750',
line: '750',
name: 'Metro 750 — Ventura Rapid',
color: '#e53935',
laneZ: 2,
stops: [
{ id: 'woodland-hills-r', label: 'Woodland Hills', lng: -118.6050, lat: 34.1683 },
{ id: 'encino-r', label: 'Encino', lng: -118.5010, lat: 34.1590 },
{ id: 'sherman-oaks-r', label: 'Sherman Oaks', lng: -118.4490, lat: 34.1510 },
{ id: 'studio-city-r', label: 'Studio City', lng: -118.3960, lat: 34.1430 }
]
},
{
id: 'metro-234',
line: '234',
name: 'Metro 234 — Sepulveda',
color: '#1e88e5',
laneZ: -8,
stops: [
{ id: 'topanga', label: 'Topanga Plaza', lng: -118.6044, lat: 34.1700 },
{ id: 'reseda', label: 'Reseda', lng: -118.5359, lat: 34.1660 },
{ id: 'van-nuys', label: 'Van Nuys', lng: -118.4485, lat: 34.1620 },
{ id: 'coldwater', label: 'Coldwater Cyn', lng: -118.4070, lat: 34.1500 }
]
},
{
id: 'metro-233',
line: '233',
name: 'Metro 233 — Van Nuys Blvd',
color: '#43a047',
laneZ: 8,
stops: [
{ id: 'calabasas', label: 'Calabasas', lng: -118.6390, lat: 34.1600 },
{ id: 'tarzana-vn', label: 'Tarzana', lng: -118.5530, lat: 34.1700 },
{ id: 'encino-vn', label: 'Encino', lng: -118.5010, lat: 34.1590 },
{ id: 'universal', label: 'Universal City', lng: -118.3520, lat: 34.1380 }
]
}
];
app.get('/api/routes', (_req, res) => {
res.json({
name: 'Ventura Boulevard Corridor',
bounds: { west: -118.6700, east: -118.3700, south: 34.1400, north: 34.1900 },
routes: ROUTES
});
});
app.get('/api/route', (_req, res) => {
const r0 = ROUTES[0];
res.json({ name: r0.name, stops: r0.stops });
});
// Browser-side config — exposes only the Maps key (which has to be browser-visible
// anyway, but we keep it out of source by serving from server-side env).
app.get('/api/config', (_req, res) => {
res.json({
googleMapsApiKey: GMAPS_KEY,
hasMaps: Boolean(GMAPS_KEY)
});
});
app.listen(PORT, () => {
console.log(`[ventura-bus-3d] listening on http://127.0.0.1:${PORT} (maps=${GMAPS_KEY ? 'on' : 'off'})`);
});