Actually have A UI for the first time in 21 weeks #5
41
.gitea/workflows/deploy.yaml
Normal file
41
.gitea/workflows/deploy.yaml
Normal 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 &
|
||||
|
||||
4059
alerts.json
Normal file
4059
alerts.json
Normal file
File diff suppressed because it is too large
Load Diff
49
public/app.js
Normal file
49
public/app.js
Normal 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
24
public/index.html
Normal 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>
|
||||
@@ -6,101 +6,147 @@ const dataDir = path.join(process.cwd(), "src", "dal", "data");
|
||||
function readJSON(name) {
|
||||
try {
|
||||
const p = path.join(dataDir, name);
|
||||
|
||||
if (!fs.existsSync(p)) return null;
|
||||
const raw = fs.readFileSync(p, "utf8");
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
return JSON.parse(fs.readFileSync(p, "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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 : Number(v.bearing),
|
||||
speed: v.speed == null ? undefined : Number(v.speed),
|
||||
location: {
|
||||
latitude: loc.latitude == null ? undefined : Number(loc.latitude),
|
||||
longitude: loc.longitude == null ? undefined : Number(loc.longitude)
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function getVehicleById(id) {
|
||||
if (id == null) return null;
|
||||
const vehicles = getVehicles();
|
||||
|
||||
|
||||
return vehicles.find(v => String(v.vehicleId) === String(id)) || null;
|
||||
}
|
||||
|
||||
export function getRoutes() {
|
||||
const explicit = readJSON("routes.json");
|
||||
if (Array.isArray(explicit) && explicit.length) return explicit;
|
||||
const raw = readJSON("routes.json");
|
||||
|
||||
const vehicles = getVehicles();
|
||||
const map = new Map();
|
||||
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());
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function getRouteById(routeId) {
|
||||
if (routeId == null) return null;
|
||||
const routes = getRoutes();
|
||||
|
||||
return routes.find(r => String(r.routeId) === String(routeId)) || null;
|
||||
|
||||
return routes.find(r => String(r.route_id) === String(routeId)) || null;
|
||||
}
|
||||
|
||||
export function getRoutePathsMap() {
|
||||
const raw = readJSON("routepaths.json");
|
||||
|
||||
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
||||
if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
|
||||
return {};
|
||||
}
|
||||
|
||||
export function getRoutePath(routeId) {
|
||||
if (routeId == null) return null;
|
||||
const map = getRoutePathsMap();
|
||||
const keys = Object.keys(map || {});
|
||||
const foundKey = keys.find(k => String(k) === String(routeId));
|
||||
|
||||
return foundKey ? map[foundKey] : null;
|
||||
return Object.prototype.hasOwnProperty.call(map, routeId) ? map[routeId] : null;
|
||||
}
|
||||
|
||||
export function getStationsRaw() {
|
||||
return readJSON("stations.json") || null;
|
||||
}
|
||||
|
||||
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 [];
|
||||
|
||||
}
|
||||
|
||||
export function getStopsByRoute(routeId) {
|
||||
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;
|
||||
if (raw == null) return [];
|
||||
if (typeof raw === "object" && !Array.isArray(raw)) {
|
||||
return Array.isArray(raw[routeId]) ? raw[routeId] : [];
|
||||
}
|
||||
|
||||
return lines.map(String).includes(String(routeId));
|
||||
});
|
||||
if (Array.isArray(raw)) {
|
||||
const matchesFromArrayMaps = [];
|
||||
for (const item of raw) {
|
||||
if (typeof item === "object" && !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, routeId)) {
|
||||
const arr = item[routeId];
|
||||
if (Array.isArray(arr)) matchesFromArrayMaps.push(...arr);
|
||||
}
|
||||
}
|
||||
if (matchesFromArrayMaps.length) return matchesFromArrayMaps;
|
||||
|
||||
return raw.filter(s => Array.isArray(s.lines) && s.lines.map(String).includes(String(routeId)));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getStationById(stationId) {
|
||||
if (stationId == null) return null;
|
||||
const stations = getStations();
|
||||
export function getStationById(id) {
|
||||
if (id == null) return null;
|
||||
const raw = getStationsRaw();
|
||||
if (raw == null) return null;
|
||||
|
||||
if (!Array.isArray(stations)) return null;
|
||||
return stations.find(s => {
|
||||
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 (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 found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}) || 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) || String(s.stationId) === String(id));
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return raw.find(s => String(s.stop_id) === String(id) || String(s.stationId) === String(id)) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -11,32 +11,43 @@ router.get("/routes", (req, res) => {
|
||||
|
||||
router.get("/routes/:routeId", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
let route = dal.getRouteById(routeId) ?? null;
|
||||
const route = dal.getRouteById(routeId);
|
||||
|
||||
if (route === null) {
|
||||
const all = dal.getRoutes();
|
||||
route = all.find(r => {
|
||||
const rid = r.route_id ?? r.routeId ?? r.route;
|
||||
return rid != null && String(rid) === String(routeId);
|
||||
}) ?? null;
|
||||
}
|
||||
if (!route) return res.status(404).json({error: "Route Was Not Found"});
|
||||
const routePath = dal.getRoutePath(routeId);
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
res.json({data: {route, routePath, stations}});
|
||||
});
|
||||
|
||||
const routePath = dal.getRoutePath(routeId) ?? null;
|
||||
let stations = dal.getStopsByRoute(routeId) ?? [];
|
||||
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 routePath = dal.getRoutePath(routeId);
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
res.json({data: {route, routePath, stations}});
|
||||
});
|
||||
|
||||
router.get("/routes/:routeId/stations", (req, res) => {
|
||||
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});
|
||||
});
|
||||
|
||||
router.get("/stations", (req, res) => {
|
||||
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});
|
||||
});
|
||||
@@ -44,8 +55,8 @@ router.get("/stations", (req, res) => {
|
||||
router.get("/station/:stationId", (req, res) => {
|
||||
const stationId = req.params.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});
|
||||
});
|
||||
|
||||
|
||||
13
start.js
13
start.js
@@ -1,7 +1,12 @@
|
||||
//Starts
|
||||
import express from "express";
|
||||
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, () => {
|
||||
console.log(`Transit API http://localhost:${PORT}`);
|
||||
});
|
||||
console.log(`Transit API running at http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
69024
tripUpdates.json
Normal file
69024
tripUpdates.json
Normal file
File diff suppressed because it is too large
Load Diff
6689
vehicles.json
Normal file
6689
vehicles.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user