diff --git a/Dockerfile b/Dockerfile
index cab5bfc..72b3cfb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,49 +1,28 @@
-# ---- Builder Stage ----
-# Use official Node.js 20 (Alpine)
FROM node:20-alpine AS builder
-# Install init system
-RUN apk add --no-cache dumb-init
-
-# App dir
WORKDIR /app
-# Copy package files
COPY package*.json ./
-# Install production deps
RUN npm ci --only=production && npm cache clean --force
-# ---- Final Stage ----
-FROM node:20-alpine
+COPY . .
-LABEL maintainer="your-email@example.com"
-LABEL org.opencontainers.image.source="https://github.com/yourname/uta-gtfs-mysql"
+RUN npm run build || echo "No build step needed"
-# Install dumb-init + MySQL client (optional but common)
-RUN apk add --no-cache dumb-init mysql-client
-
-# Create non-root user
-RUN addgroup -g 1001 -S nodejs \
- && adduser -S utauser -u 1001
+FROM node:20-alpine AS production
WORKDIR /app
-# Copy node_modules from builder
COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app ./
-# Copy app source
-COPY . .
+RUN addgroup -g 1001 -S nodejs && \
+ adduser -S uta-sync -u 1001
-RUN chown -R utauser:nodejs /app
-USER utauser
+USER uta-sync
-# Healthcheck – you may want to change port if needed
-HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
- CMD wget -qO- http://localhost:3000/ || exit 1
+EXPOSE 1001
-EXPOSE 3000
-
-ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
diff --git a/docker-compose.yaml b/docker-compose.yaml
index ac53863..45f27ef 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -1,45 +1,19 @@
-version: '3.9'
-
services:
- uta-mysql:
- image: mysql:8.0
- container_name: uta-mysql
- restart: unless-stopped
- environment:
- MYSQL_ROOT_PASSWORD: uta123
- MYSQL_DATABASE: uta
- MYSQL_USER: uta
- MYSQL_PASSWORD: uta123
- command: --default-authentication-plugin=mysql_native_password
- volumes:
- - mysql_data:/var/lib/mysql
- - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
+ redis:
+ image: redis:7-alpine
+ container_name: my-redis
ports:
- - "3306:3306"
- healthcheck:
- test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-puta", "--password=uta123"]
- interval: 10s
- timeout: 5s
- retries: 5
+ - "6379:6379"
+ restart: unless-stopped
- uta-sync:
+ app:
build: .
- container_name: uta-sync
+ container_name: my-express-app
ports:
- "1001:1001"
restart: unless-stopped
environment:
- - DATABASE_URL=mysql://uta:uta123@uta-mysql:3306/uta
- - HEALTH_PORT=3000
- - NODE_ENV=production
+ REDIS_HOST: redis
+ REDIS_PORT: 6379
depends_on:
- uta-mysql:
- condition: service_healthy
- healthcheck:
- test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
- interval: 30s
- timeout: 15s
- retries: 3
-
-volumes:
- mysql_data:
+ - redis
diff --git a/init-db.sql b/init-db.sql
deleted file mode 100644
index 472d4e9..0000000
--- a/init-db.sql
+++ /dev/null
@@ -1,98 +0,0 @@
-CREATE TABLE routes (
- route_id VARCHAR(50) PRIMARY KEY,
- agency_id VARCHAR(50),
- route_short_name VARCHAR(50),
- route_long_name VARCHAR(255),
- route_desc TEXT,
- route_type INT,
- route_url VARCHAR(255),
- route_color VARCHAR(10),
- route_text_color VARCHAR(10)
-);
-
-CREATE TABLE trips (
- trip_id VARCHAR(50) PRIMARY KEY,
- route_id VARCHAR(50),
- service_id VARCHAR(50),
- shape_id VARCHAR(50),
- trip_headsign VARCHAR(255),
- trip_short_name VARCHAR(50),
- direction_id INT,
- block_id VARCHAR(50),
- wheelchair_accessible INT,
- bikes_allowed INT,
- FOREIGN KEY (route_id) REFERENCES routes(route_id)
-);
-CREATE TABLE stops (
- stop_id VARCHAR(50) PRIMARY KEY,
- stop_code VARCHAR(50),
- stop_name VARCHAR(255),
- stop_desc TEXT,
- stop_lat DOUBLE,
- stop_lon DOUBLE,
- zone_id VARCHAR(50),
- stop_url VARCHAR(255),
- location_type INT DEFAULT NULL,
- parent_station VARCHAR(50),
- stop_timezone VARCHAR(50),
- wheelchair_boarding INT DEFAULT NULL
-);
-CREATE TABLE stop_times (
- trip_id VARCHAR(50),
- arrival_time VARCHAR(20),
- departure_time VARCHAR(20),
- stop_id VARCHAR(50),
- stop_sequence INT,
- stop_headsign VARCHAR(255),
- pickup_type INT,
- drop_off_type INT,
- shape_dist_traveled DOUBLE NOT NULL DEFAULT 0
- timepoint INT,
- PRIMARY KEY (trip_id, stop_id, stop_sequence),
- FOREIGN KEY (trip_id) REFERENCES trips(trip_id),
- FOREIGN KEY (stop_id) REFERENCES stops(stop_id)
-);
-
-CREATE TABLE shapes (
- shape_id VARCHAR(50),
- shape_pt_lat DOUBLE,
- shape_pt_lon DOUBLE,
- shape_pt_sequence INT,
- shape_dist_traveled DOUBLE,
- PRIMARY KEY (shape_id, shape_pt_sequence)
-);
-
--- Realtime: vehicle positions
-CREATE TABLE rt_vehicle_positions (
- vehicle_id VARCHAR(50),
- trip_id VARCHAR(50),
- route_id VARCHAR(50),
- lat DOUBLE,
- lon DOUBLE,
- bearing INT,
- speed DOUBLE,
- timestamp BIGINT,
- PRIMARY KEY (vehicle_id)
-);
-
--- Realtime: trip updates
-CREATE TABLE rt_trip_updates (
- trip_id VARCHAR(50),
- stop_id VARCHAR(50),
- arrival_time BIGINT,
- departure_time BIGINT,
- delay INT,
- schedule_relationship VARCHAR(20),
- PRIMARY KEY (trip_id, stop_id),
- FOREIGN KEY (stop_id) REFERENCES stops(stop_id)
-);
-
--- Realtime: alerts
-CREATE TABLE rt_alerts (
- alert_id VARCHAR(50) PRIMARY KEY,
- header TEXT,
- description TEXT,
- cause VARCHAR(50),
- effect VARCHAR(50),
- timestamp BIGINT
-);
diff --git a/package-lock.json b/package-lock.json
index f432929..54bd089 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"node-fetch": "^3.3.2",
"parse": "^7.1.2",
"pg": "^8.16.3",
+ "redis": "^5.10.0",
"unzipper": "^0.12.3"
}
},
@@ -154,6 +155,67 @@
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
+ "node_modules/@redis/bloom": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz",
+ "integrity": "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@redis/client": "^5.10.0"
+ }
+ },
+ "node_modules/@redis/client": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz",
+ "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "cluster-key-slot": "1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@redis/json": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.10.0.tgz",
+ "integrity": "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@redis/client": "^5.10.0"
+ }
+ },
+ "node_modules/@redis/search": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.10.0.tgz",
+ "integrity": "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@redis/client": "^5.10.0"
+ }
+ },
+ "node_modules/@redis/time-series": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.10.0.tgz",
+ "integrity": "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@redis/client": "^5.10.0"
+ }
+ },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -362,6 +424,15 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1785,6 +1856,22 @@
"util-deprecate": "~1.0.1"
}
},
+ "node_modules/redis": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/redis/-/redis-5.10.0.tgz",
+ "integrity": "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==",
+ "license": "MIT",
+ "dependencies": {
+ "@redis/bloom": "5.10.0",
+ "@redis/client": "5.10.0",
+ "@redis/json": "5.10.0",
+ "@redis/search": "5.10.0",
+ "@redis/time-series": "5.10.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/requizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
diff --git a/package.json b/package.json
index cf27eb8..dcdb623 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"node-fetch": "^3.3.2",
"parse": "^7.1.2",
"pg": "^8.16.3",
+ "redis": "^5.10.0",
"unzipper": "^0.12.3"
}
}
diff --git a/public/app.js b/public/app.js
deleted file mode 100644
index d110b19..0000000
--- a/public/app.js
+++ /dev/null
@@ -1,49 +0,0 @@
-const map = L.map("map").setView([40.7608, -111.8910], 12);
-
-L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
- attribution: '© OpenStreetMap',
-}).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();
diff --git a/public/index.html b/public/index.html
index d4943aa..316b125 100644
--- a/public/index.html
+++ b/public/index.html
@@ -19,6 +19,7 @@