10 Commits

Author SHA1 Message Date
fbb99f21da work in progress 2025-12-01 17:51:38 -07:00
Edward-1100
06f3129fb9 routePath + Stops Location{}
Some checks failed
Deploy Express API / deploy (push) Has been cancelled
2025-11-28 16:17:28 -07:00
bfe47fb443 Merge branch 'main' into backend
Some checks failed
Deploy Express API / deploy (push) Has been cancelled
2025-11-24 18:23:38 -08:00
a5fceda42f basic frontend js
Some checks failed
Deploy Express API / deploy (push) Has been cancelled
2025-11-24 19:20:15 -07:00
Edward-1100
eaf76f94a1 Updated routes
Some checks failed
Deploy Express API / deploy (push) Has been cancelled
2025-11-24 18:32:45 -07:00
7cb29179a2 Update deploy.yaml
Some checks failed
Deploy Express API / deploy (push) Has been cancelled
2025-11-24 17:05:29 -07:00
8a5db40d1d test cD 2025-11-24 17:02:59 -07:00
9c51163505 fixed port conflict for webserver gitea 2025-11-24 16:54:20 -07:00
ba32d9d482 Create deploy.yaml 2025-11-24 16:49:51 -07:00
a7aff8e169 version 2.0 json data outlook 2025-11-24 16:37:12 -07:00
14 changed files with 80358 additions and 73 deletions

View File

@@ -0,0 +1,41 @@
name: Deploy Express API
on:
push:
branches:
- backend
- main
jobs:
deploy:
runs-on: linux_amd64
steps:
# Checkout code
- uses: actions/checkout@v3
# Pull latest code
- name: Pull latest code
run: |
cd /var/www/myapp/Project
git reset --hard
git pull origin main
# Install dependencies
- name: Install dependencies
run: |
cd /var/www/myapp/Project
npm install
# Kill existing process (optional)
- name: Stop previous instance
run: |
pkill -f "node .*app.js" || true
pkill -f "node .*start.js" || true
# Start app in background
- name: Start app
run: |
cd /var/www/myapp/Project
nohup npm start > app.log 2>&1 &

View File

@@ -64,4 +64,4 @@
67 6767

4059
alerts.json Normal file

File diff suppressed because it is too large Load Diff

150
package-lock.json generated
View File

@@ -10,7 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.1.0" "express": "^5.1.0",
"pg": "^8.16.3"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@@ -581,6 +582,135 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/pg": {
"version": "8.16.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
"pg-protocol": "^1.10.3",
"pg-types": "2.2.0",
"pgpass": "1.0.5"
},
"engines": {
"node": ">= 16.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.2.7"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
"license": "MIT",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
"license": "MIT"
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"license": "ISC",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
"license": "MIT",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
"license": "MIT"
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"license": "MIT",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"license": "MIT",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"license": "MIT",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -786,6 +916,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -841,6 +980,15 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC" "license": "ISC"
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
} }
} }
} }

View File

@@ -17,6 +17,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^5.1.0" "express": "^5.1.0",
"pg": "^8.16.3"
} }
} }

49
public/app.js Normal file
View File

@@ -0,0 +1,49 @@
const map = L.map("map").setView([40.7608, -111.8910], 12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
const API_URL = "http://localhost:7653/api/v0/";
const trainEmojiIcon = L.divIcon({ html: "🔵", className: "", iconSize: [64,64], iconAnchor: [16,32], popupAnchor: [0,-32] });
const stopsEmojiIcon = L.divIcon({ html: "🫃", className: "", iconSize: [64,64], iconAnchor: [16,32], popupAnchor: [0,-32] });
function addMarker(lat, lon, content, icon) {
if (!isNaN(lat) && !isNaN(lon)) {
const marker = L.marker([lat, lon], { icon: icon }).addTo(map);
if (content) marker.bindPopup(content);
} else {
console.warn("Invalid coordinates:", latitude, longitude);
}
}
function getTrainsByRoute(route) {
fetch(API_URL + 'vehicles')
.then(res => res.json())
.then(data => {
const trains = data.data;
const filtered = route ? trains.filter(t => t.routeId == route) : trains;
filtered.forEach(t => {
addMarker(t.location.latitude, t.location.longitude, t.routeName + ": Vehicle " + t.vehicleId, trainEmojiIcon);
});
})
.catch(err => console.error("Error fetching trains:", err));
}
function getStopsByRoute(route) {
fetch(API_URL + 'stops/' + route)
.then(res => res.json())
.then(data => {
const stops = data.data;
stops.forEach(s => {
const lat = parseFloat(s.stop_lat);
const lon = parseFloat(s.stop_lon);
addMarker(lat,lon, s.stop_name + " - " + s.stop_desc, stopsEmojiIcon);
});
})
.catch(err => console.error("Error fetching stops:", err));
}
getStopsByRoute("701");
getTrainsByRoute();

24
public/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Traxer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
crossorigin=""
/>
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
crossorigin=""
></script>
</head>
<body>
<div id="map" class="w-full h-screen"></div>
</body>
<script src="app.js"></script>
</html>

View File

@@ -6020,7 +6020,7 @@
] ]
}, },
{ {
"701": "703":
[ [
{ {
"shape_id": "238255", "shape_id": "238255",

142
src/dal/postgisdDal.js Normal file
View File

@@ -0,0 +1,142 @@
import pkg from "pg";
const { Pool } = pkg;
const pool = new Pool({
user: "nate",
host: "localhost",
database: "gtfs",
password: "",
port: 5432,
});
// ---------------------- VEHICLES ----------------------
export async function getVehicles({ minLat, maxLat, minLng, maxLng, routeNum } = {}) {
let sql = `
SELECT vehicle_id, trip_id, route_id, ts, speed,
ST_Y(geom::geometry) AS latitude,
ST_X(geom::geometry) AS longitude
FROM rt_vehicles
`;
const params = [];
const conditions = [];
if (routeNum) {
conditions.push(`route_id = $${params.length + 1}`);
params.push(routeNum);
}
if (minLat != null) {
conditions.push(`ST_Y(geom::geometry) >= $${params.length + 1}`);
params.push(minLat);
}
if (maxLat != null) {
conditions.push(`ST_Y(geom::geometry) <= $${params.length + 1}`);
params.push(maxLat);
}
if (minLng != null) {
conditions.push(`ST_X(geom::geometry) >= $${params.length + 1}`);
params.push(minLng);
}
if (maxLng != null) {
conditions.push(`ST_X(geom::geometry) <= $${params.length + 1}`);
params.push(maxLng);
}
if (conditions.length) {
sql += " WHERE " + conditions.join(" AND ");
}
const { rows } = await pool.query(sql, params);
return rows.map(v => ({
vehicleId: v.vehicle_id,
tripId: v.trip_id,
routeId: v.route_id,
ts: Number(v.ts),
speed: v.speed,
location: { latitude: v.latitude, longitude: v.longitude }
}));
}
export async function getVehicleById(vehicleId) {
const sql = `
SELECT vehicle_id, trip_id, route_id, ts, speed,
ST_Y(geom::geometry) AS latitude,
ST_X(geom::geometry) AS longitude
FROM rt_vehicles
WHERE vehicle_id = $1
LIMIT 1
`;
const { rows } = await pool.query(sql, [vehicleId]);
if (!rows[0]) return null;
const v = rows[0];
return {
vehicleId: v.vehicle_id,
tripId: v.trip_id,
routeId: v.route_id,
ts: Number(v.ts),
speed: v.speed,
location: { latitude: v.latitude, longitude: v.longitude }
};
}
// ---------------------- ROUTES ----------------------
export async function getRoutes() {
const { rows } = await pool.query(`SELECT route_id, short_name, long_name, name FROM gtfs_routes`);
return rows;
}
export async function getRouteById(routeId) {
const { rows } = await pool.query(
`SELECT route_id, short_name, long_name, name FROM gtfs_routes WHERE route_id = $1 LIMIT 1`,
[routeId]
);
return rows[0] || null;
}
// ---------------------- STOPS ----------------------
export async function getStops() {
const { rows } = await pool.query(`
SELECT stop_id, stop_name, stop_lat, stop_lon
FROM gtfs_stops
`);
return rows;
}
export async function getStopById(stopId) {
const { rows } = await pool.query(`
SELECT stop_id, stop_name, stop_lat, stop_lon
FROM gtfs_stops
WHERE stop_id = $1
LIMIT 1
`, [stopId]);
return rows[0] || null;
}
// ---------------------- STOPS BY ROUTE ----------------------
export async function getStopsByRoute(routeId) {
const sql = `
SELECT s.stop_id, s.stop_name, s.stop_lat, s.stop_lon
FROM gtfs_stop_times st
JOIN gtfs_trips t ON st.trip_id = t.trip_id
JOIN gtfs_stops s ON st.stop_id = s.stop_id
WHERE t.route_id = $1
GROUP BY s.stop_id, s.stop_name, s.stop_lat, s.stop_lon
ORDER BY MIN(st.stop_sequence)
`;
const { rows } = await pool.query(sql, [routeId]);
return rows;
}
// ---------------------- TRIP SCHEDULE ----------------------
export async function getScheduleByTrip(tripId) {
const sql = `
SELECT st.stop_sequence, s.stop_id, s.stop_name, st.arrival_time, st.departure_time
FROM gtfs_stop_times st
JOIN gtfs_stops s ON st.stop_id = s.stop_id
WHERE st.trip_id = $1
ORDER BY st.stop_sequence
`;
const { rows } = await pool.query(sql, [tripId]);
return rows;
}

View File

@@ -6,16 +6,41 @@ const dataDir = path.join(process.cwd(), "src", "dal", "data");
function readJSON(name) { function readJSON(name) {
try { try {
const p = path.join(dataDir, name); const p = path.join(dataDir, name);
if (!fs.existsSync(p)) return null; if (!fs.existsSync(p)) return null;
const raw = fs.readFileSync(p, "utf8"); return JSON.parse(fs.readFileSync(p, "utf8"));
return JSON.parse(raw); } catch {
} catch (err) {
return null; return null;
} }
} }
function toNumber(v) {
const n = Number(v);
return Number.isFinite(n) ? n : undefined;
}
export function getVehicles() { export function getVehicles() {
return readJSON("vehicles.json") || []; const raw = readJSON("vehicles.json");
if (!Array.isArray(raw)) return [];
return raw.map(v => {
const loc = v.location || {};
return {
vehicleId: v.vehicleId,
routeNum: v.routeNum,
routeName: v.routeName,
destination: v.destination,
bearing: v.bearing == null ? undefined : toNumber(v.bearing),
speed: v.speed == null ? undefined : toNumber(v.speed),
location: {
latitude: toNumber(loc.latitude),
longitude: toNumber(loc.longitude)
}
};
});
} }
export function getVehicleById(id) { export function getVehicleById(id) {
@@ -26,81 +51,142 @@ export function getVehicleById(id) {
} }
export function getRoutes() { export function getRoutes() {
const explicit = readJSON("routes.json"); const raw = readJSON("routes.json");
if (Array.isArray(explicit) && explicit.length) return explicit;
const vehicles = getVehicles(); if (!Array.isArray(raw)) return [];
const map = new Map(); return raw;
vehicles.forEach(v => {
const key = String(v.routeNum ?? "");
if (!map.has(key)) {
map.set(key, {
routeId: key,
routeName: v.routeName ?? null,
startTime: null,
endTime: null,
trains: []
});
}
map.get(key).trains.push(v.vehicleId);
});
return Array.from(map.values());
} }
export function getRouteById(routeId) { export function getRouteById(routeId) {
if (routeId == null) return null; if (routeId == null) return null;
const routes = getRoutes(); const routes = getRoutes();
return routes.find(r => String(r.routeId) === String(routeId)) || null; return routes.find(r => String(r.route_id) === String(routeId)) || null;
}
function readRoutepathsRaw() {
const raw = readJSON("routePath.json");
if (!Array.isArray(raw)) return [];
return raw;
} }
export function getRoutePathsMap() { export function getRoutePathsMap() {
const raw = readJSON("routepaths.json"); const raw = readRoutepathsRaw();
const map = {};
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {}; for (const item of raw) {
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
const keys = Object.keys(item);
if (keys.length !== 1) continue;
const k = String(keys[0]);
if (Array.isArray(item[k])) map[k] = item[k];
}
return map;
} }
export function getRoutePath(routeId) { export function getRoutePath(routeId) {
if (routeId == null) return null; if (routeId == null) return null;
const map = getRoutePathsMap(); const map = getRoutePathsMap();
const keys = Object.keys(map || {}); const key = String(routeId);
const foundKey = keys.find(k => String(k) === String(routeId));
return foundKey ? map[foundKey] : null; return Object.prototype.hasOwnProperty.call(map, key) ? map[key] : null;
}
export function getStationsRaw() {
return readJSON("stations.json") || null;
} }
export function getStations() { export function getStations() {
return readJSON("stations.json") || []; const raw = getStationsRaw();
if (raw == null) return [];
if (Array.isArray(raw)) {
const isArrayOfMaps = raw.every(item => typeof item === "object" && !Array.isArray(item) && Object.values(item).every(v => Array.isArray(v)));
if (isArrayOfMaps) return raw.flatMap(item => Object.values(item).flat());
return raw;
}
if (typeof raw === "object") {
return Object.values(raw).flat();
}
return [];
}
function transformationStation(s) {
return {
stop_id: s.stop_id,
stop_code: s.stop_code,
stop_name: s.stop_name,
stop_desc: s.stop_desc,
location: { latitude: toNumber(s.stop_lat), longitude: toNumber(s.stop_lon) },
stop_url: s.stop_url,
location_type: s.location_type,
parent_station: s.parent_station,
lines: s.lines
};
} }
export function getStopsByRoute(routeId) { export function getStopsByRoute(routeId) {
if (routeId == null) return []; if (routeId == null) return [];
const stations = getStations(); const raw = getStationsRaw();
if (!Array.isArray(stations)) return [];
return stations.filter(s => {
const lines = s.lines ?? s.lines_arr ?? s.line_ids ?? null;
if (!Array.isArray(lines)) return false;
return lines.map(String).includes(String(routeId));
});
if (raw == null) return [];
if (typeof raw === "object" && !Array.isArray(raw)) {
const arr = raw[routeId];
if (!Array.isArray(arr)) return [];
return arr.map(transformationStation);
} }
export function getStationById(stationId) { if (Array.isArray(raw)) {
if (stationId == null) return null; const matchesFromArrayMaps = [];
const stations = getStations(); for (const item of raw) {
if (typeof item === "object" && !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, routeId)) {
if (!Array.isArray(stations)) return null; const arr = item[routeId];
return stations.find(s => { if (Array.isArray(arr)) matchesFromArrayMaps.push(...arr.map(transformationStation));
if (s.stop_id && String(s.stop_id) === String(stationId)) return true; }
if (s.stationId && String(s.stationId) === String(stationId)) return true; }
if (s.id && String(s.id) === String(stationId)) return true; if (matchesFromArrayMaps.length) return matchesFromArrayMaps;
return false; return raw.filter(s => Array.isArray(s.lines) && s.lines.map(String).includes(String(routeId))).map(transformationStation);
}) || null; }
return [];
}
export function getStationById(id) {
if (id == null) return null;
const raw = getStationsRaw();
if (raw == null) return null;
if (typeof raw === "object" && !Array.isArray(raw)) {
for (const key of Object.keys(raw)) {
const arr = raw[key];
if (!Array.isArray(arr)) continue;
const found = arr.find(s => String(s.stop_id) === String(id) || String(s.stationId) === String(id));
if (found) return transformationStation(found);
}
return null;
}
if (Array.isArray(raw)) {
const isArrayOfMaps = raw.every(item => typeof item === "object" && !Array.isArray(item) && Object.values(item).every(v => Array.isArray(v)));
if (isArrayOfMaps) {
for (const item of raw) {
for (const key of Object.keys(item)) {
const arr = item[key];
const found = arr.find(s => String(s.stop_id) === String(id));
if (found) return transformationStation(found);
}
}
return null;
}
const found = raw.find(s => String(s.stop_id) === String(id) || String(s.stationId) === String(id));
return found ? transformationStation(found) : null;
}
return null;
} }

View File

@@ -11,32 +11,49 @@ router.get("/routes", (req, res) => {
router.get("/routes/:routeId", (req, res) => { router.get("/routes/:routeId", (req, res) => {
const routeId = req.params.routeId; const routeId = req.params.routeId;
let route = dal.getRouteById(routeId) ?? null; const route = dal.getRouteById(routeId);
if (route === null) { if (!route) return res.status(404).json({error: "Route Was Not Found"});
const all = dal.getRoutes(); const stations = dal.getStopsByRoute(routeId);
route = all.find(r => {
const rid = r.route_id ?? r.routeId ?? r.route;
return rid != null && String(rid) === String(routeId);
}) ?? null;
}
const routePath = dal.getRoutePath(routeId) ?? null; res.json({data: {route, stations}});
let stations = dal.getStopsByRoute(routeId) ?? []; });
res.json({data: {route, routePath, stations}}); router.get("/route/:routeId", (req, res) => {
const routeId = req.params.routeId;
const route = dal.getRouteById(routeId);
if (!route) return res.status(404).json({error: "Route Was Not Found"});
const stations = dal.getStopsByRoute(routeId);
res.json({data: {route, stations}});
});
router.get("/routepaths/:routeId", (req, res) => {
const routeId = req.params.routeId;
const rp = dal.getRoutePath(routeId);
if (!rp) return res.status(404).json({error: "RoutePath Was Not Found"});
res.json({meta: {routeId: String(routeId), returned: Array.isArray(rp) ? rp.length : 0}, data: rp});
}); });
router.get("/routes/:routeId/stations", (req, res) => { router.get("/routes/:routeId/stations", (req, res) => {
const routeId = req.params.routeId; const routeId = req.params.routeId;
const stations = dal.getStopsByRoute(routeId) ?? []; const stations = dal.getStopsByRoute(routeId);
res.json({meta: {routeId: String(routeId), returned: stations.length}, data: stations});
});
router.get("/stops/:routeId", (req, res) => {
const routeId = req.params.routeId;
const stations = dal.getStopsByRoute(routeId);
res.json({meta: {routeId: String(routeId), returned: stations.length}, data: stations}); res.json({meta: {routeId: String(routeId), returned: stations.length}, data: stations});
}); });
router.get("/stations", (req, res) => { router.get("/stations", (req, res) => {
const route = req.query.route; const route = req.query.route;
const stations = route ? (dal.getStopsByRoute(route) ?? []) : (dal.getStations() ?? []); const stations = route ? dal.getStopsByRoute(route) : dal.getStations();
res.json({meta: {returned: stations.length}, data: stations}); res.json({meta: {returned: stations.length}, data: stations});
}); });
@@ -44,8 +61,8 @@ router.get("/stations", (req, res) => {
router.get("/station/:stationId", (req, res) => { router.get("/station/:stationId", (req, res) => {
const stationId = req.params.stationId; const stationId = req.params.stationId;
const station = dal.getStationById(stationId); const station = dal.getStationById(stationId);
if (!station) return res.status(404).json({error: "Station Was Not Found"});
if (!station) return res.status(404).json({error: "Station Was Not Found"});
res.json({data: station}); res.json({data: station});
}); });

View File

@@ -1,7 +1,12 @@
//Starts import express from "express";
import app from "./app.js"; import app from "./app.js";
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 7653;
// Serve static files from "public" folder
app.use(express.static('public'));
// Start the server
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Transit API http://localhost:${PORT}`); console.log(`Transit API running at http://localhost:${PORT}`);
}); });

69024
tripUpdates.json Normal file

File diff suppressed because it is too large Load Diff

6689
vehicles.json Normal file

File diff suppressed because it is too large Load Diff