Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Git
.git
.gitignore
.github

# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
.venv
*.egg-info/
.eggs/
dist/
build/
.pytest_cache/
.python-version

# Node
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn
client/node_modules/
client/dist/
client/.vite/
test-client/node_modules/
test-client/dist/

# Environment
.env
.env.local
.env.*.local
.flaskenv

# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store

# Logs
*.log
logs/

# Database
*.db
*.sqlite
*.sqlite3
instance/

# Testing
.coverage
htmlcov/
.tox/

# Documentation
*.md
!README.md

# Misc
*.bak
*.tmp
.yamllint
62 changes: 62 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# =============================================================================
# SHUBBLE ENVIRONMENT CONFIGURATION
# =============================================================================
# Copy this file to .env and update with your values

# =============================================================================
# SERVICE PORTS (Docker)
# =============================================================================
# Configure which ports services are exposed on the host machine
FRONTEND_PORT=3000
BACKEND_PORT=8000
POSTGRES_PORT=5432
REDIS_PORT=6379
TEST_FRONTEND_PORT=5174
TEST_BACKEND_PORT=4000

# =============================================================================
# SERVICE URLS
# =============================================================================
# Configure URLs for all services
# Format: http://host:port (do not include trailing slash)

# Main application URLs
FRONTEND_URL=http://localhost:3000
VITE_FRONTEND_URL=http://localhost:5173
VITE_BACKEND_URL=http://localhost:8000

# Test/Mock service URLs (for development/testing)
TEST_FRONTEND_URL=http://localhost:5174
VITE_TEST_FRONTEND_URL=http://localhost:5174
VITE_TEST_BACKEND_URL=http://localhost:4000

# =============================================================================
# DATABASE
# =============================================================================
# PostgreSQL credentials
POSTGRES_DB=shubble
POSTGRES_USER=shubble
POSTGRES_PASSWORD=shubble

# PostgreSQL connection string
DATABASE_URL=postgresql://shubble:shubble@localhost:5432/shubble

# =============================================================================
# REDIS CACHE
# =============================================================================
# Redis connection string
REDIS_URL=redis://localhost:6379/0

# =============================================================================
# FLASK CONFIGURATION
# =============================================================================
FLASK_ENV=development
FLASK_DEBUG=true
LOG_LEVEL=INFO

# =============================================================================
# SAMSARA API (Optional - for production)
# =============================================================================
# Leave empty to use Mock Samsara API (test-server) in development
API_KEY=
SAMSARA_SECRET=
28 changes: 28 additions & 0 deletions .env.prod.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# postgres
POSTGRES_DB=shubble
POSTGRES_USER=shubble
POSTGRES_PASSWORD=shubble
POSTGRES_PORT=5432

# python env variable
DATABASE_URL=postgresql://shubble:shubble@postgres:5432/shubble
FLASK_ENV=production
FLASK_DEBUG=false
LOG_LEVEL=INFO

# Backend Docker
FRONTEND_URL=http://localhost:3000
BACKEND_PORT=8000

# Secrets
API_KEY=
SAMSARA_SECRET=

# redis
REDIS_PORT=6379
REDIS_URL=redis://redis:6379/0

# Frontend Docker
FRONTEND_PORT=3000
# Vite
VITE_BACKEND_URL=http://localhost:8000
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

__pycache__/
**__pycache__/
node_modules/
.env
client/dist
Expand Down
1 change: 0 additions & 1 deletion .python-version

This file was deleted.

3 changes: 2 additions & 1 deletion client/src/components/MapKitMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useRef, useState, useMemo } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import '../styles/MapKitMap.css';
import ShuttleIcon from "./ShuttleIcon";
import config from "../ts/config";

import type { ShuttleRouteData, ShuttleStopData } from "../ts/types/route";
import type { VehicleInformationMap } from "../ts/types/vehicleLocation";
Expand Down Expand Up @@ -186,7 +187,7 @@ export default function MapKitMap({ routeData, displayVehicles = true, generateR

const pollLocation = async () => {
try {
const response = await fetch('/api/locations');
const response = await fetch(`${config.apiBaseUrl}/api/locations`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
Expand Down
3 changes: 2 additions & 1 deletion client/src/pages/Data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "../styles/Data.css"
import DataBoard from '../components/DataBoard';
import ShuttleRow from '../components/ShuttleRow';
import type { VehicleInformationMap } from '../ts/types/vehicleLocation';
import config from '../ts/config';

export default function Data() {

Expand All @@ -14,7 +15,7 @@ export default function Data() {

const fetchShuttleData = async () => {
try {
const response = await fetch('/api/today');
const response = await fetch(`${config.apiBaseUrl}/api/today`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
Expand Down
5 changes: 3 additions & 2 deletions client/src/ts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const isStaging = import.meta.env.VITE_DEPLOY_MODE !== 'production';

const config = {
isStaging,
isDev: isStaging || import.meta.env.DEV
isDev: isStaging || import.meta.env.DEV,
apiBaseUrl: import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000'
};

export default config;
export default config;
12 changes: 6 additions & 6 deletions data/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def match_shuttles_to_schedules(cls):
# Determine day from first timestamp
required_cols = {"vehicle_id", "timestamp", "route_name"}

if at_stops.empty or not required_cols.issubset(at_stops.columns):
if not at_stops or not required_cols.issubset(at_stops.columns):
logger.warning("at_stops is missing required data returning empty match.")
return {}

Expand Down Expand Up @@ -139,15 +139,15 @@ def match_shuttles_to_schedules(cls):
# Precompute minute-aligned timestamps
at_stops['minute'] = at_stops['timestamp'].dt.floor('min')


# Group logs
shuttle_groups = {k: v for k, v in at_stops.groupby('vehicle_id')}



# Build cost matrix
for i, shuttle in enumerate(shuttles):

logs = shuttle_groups.get(shuttle)
if logs is None or logs.empty:
W[i] = 1 #No data for shuttle
Expand Down Expand Up @@ -176,6 +176,6 @@ def match_shuttles_to_schedules(cls):
cache.set("schedule_entries", result, timeout=3600)

return result

if __name__ == "__main__":
result = Schedule.match_shuttles_to_schedules()
result = Schedule.match_shuttles_to_schedules()
136 changes: 136 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-shubble}
POSTGRES_USER: ${POSTGRES_USER:-shubble}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-shubble}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "${POSTGRES_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U shubble"]
interval: 10s
timeout: 5s
retries: 5
profiles:
- backend
- test

redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
ports:
- "${REDIS_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
profiles:
- backend
- test

backend:
build:
context: .
dockerfile: docker/Dockerfile.backend
ports:
- "${BACKEND_PORT:-8000}:8000"
extra_hosts:
- "localhost:host-gateway"
environment:
FRONTEND_URL: ${FRONTEND_URL:-http://localhost:3000}
DATABASE_URL: ${DATABASE_URL:-postgresql://shubble:shubble@postgres:5432/shubble}
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
FLASK_ENV: ${FLASK_ENV:-development}
FLASK_DEBUG: ${FLASK_DEBUG:-true}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
API_KEY: ${API_KEY:-}
SAMSARA_SECRET: ${SAMSARA_SECRET:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
profiles:
- backend

worker:
build:
context: .
dockerfile: docker/Dockerfile.worker
extra_hosts:
- "localhost:host-gateway"
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql://shubble:shubble@postgres:5432/shubble}
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
FLASK_ENV: ${FLASK_ENV:-development}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
API_KEY: ${API_KEY:-}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
backend:
condition: service_started
restart: unless-stopped
profiles:
- backend

frontend:
build:
context: .
dockerfile: docker/Dockerfile.frontend
ports:
- "${FRONTEND_PORT:-3000}:80"
environment:
VITE_BACKEND_URL: ${VITE_BACKEND_URL:-http://localhost:8000}
restart: unless-stopped
profiles:
- frontend

test-server:
build:
context: .
dockerfile: docker/Dockerfile.test-server
ports:
- "${TEST_BACKEND_PORT:-4000}:4000"
extra_hosts:
- "localhost:host-gateway"
environment:
TEST_FRONTEND_URL: ${TEST_FRONTEND_URL:-http://localhost:5174}
DATABASE_URL: ${DATABASE_URL:-postgresql://shubble:shubble@postgres:5432/shubble}
REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
FLASK_ENV: ${FLASK_ENV:-development}
FLASK_DEBUG: ${FLASK_DEBUG:-true}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
profiles:
- test

test-client:
build:
context: .
dockerfile: docker/Dockerfile.test-client
ports:
- "${TEST_FRONTEND_PORT:-5174}:80"
environment:
VITE_TEST_BACKEND_URL: ${VITE_TEST_BACKEND_URL:-http://localhost:4000}
restart: unless-stopped
profiles:
- test

volumes:
postgres_data:
redis_data:
Loading