← 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'})`);
});