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 @@