Skip to content

Aboyang/parkpulse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ParkPulse

ParkPulse SG allows you to locate nearest carparks to your destination, view their real-time slot availability, and navigate to them.

Screenshot 2026-04-09 at 1 28 43 PM Screenshot 2026-04-09 at 1 44 42 PM Screenshot 2026-04-09 at 1 44 56 PM Screenshot 2026-04-09 at 1 45 10 PM Screenshot 2026-04-09 at 1 45 48 PM

Watch video demo here: https://youtu.be/AdSQKVfwq1s


Prerequisites


Setup

1. Configure environment variables

Create a .env file in the project root:

cp server/.env.example .env

Fill in the values:

# OneMap geocoding
ONEMAP_API_KEY=

# data.gov.sg carpark availability
DATA_GOV_API_KEY=

# HDB carpark dataset
DATASET_ID=d_23f946fa557947f93a8043bbef41dd09

# AWS credentials
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=ap-southeast-1

# AWS Cognito
USER_POOL_ID=
APP_CLIENT_ID=
APP_CLIENT_SECRET=

Where to get the keys:


2. Start the backend (Docker)

The backend runs as three services — the Node.js server (scalable), Nginx (load balancer), and Redis (cache).

cd server
docker compose up --build --scale server=3
Service Exposed at
API (via Nginx) http://localhost:8080
Redis internal only

To stop:

docker compose down

3. Start the frontend

cd frontend
npm install
npm run dev

The frontend will be available at http://localhost:5173 and talks to the backend at http://localhost:8080.


Running without Docker (local dev)

If you prefer to run the backend directly without Docker, you need Redis running locally first.

# Start Redis (macOS with Homebrew)
brew services start redis

# Start the backend (with hot reload)
cd server
npm install
npm run dev

The backend will be available at http://localhost:3000. Update VITE_API_BASE_URL in your .env or frontend/src/lib/config.js if needed.


Project Structure

.
├── .env                            # environment variables (not committed)
├── frontend/                       # React 18 + Vite SPA
│   └── src/
│       ├── App.jsx
│       ├── main.jsx
│       ├── pages.config.js
│       ├── api/                    # localStorage-backed mock data layer
│       ├── components/
│       │   ├── carpark/            # carpark-specific UI components
│       │   └── ui/                 # shadcn/Radix primitives (button, badge, dialog, …)
│       ├── hooks/
│       ├── lib/
│       ├── pages/
│       │   ├── Auth.jsx            # login / signup
│       │   ├── Home.jsx            # search + radius + EV filter
│       │   ├── Carparks.jsx        # search results list
│       │   ├── Carpark.jsx         # individual carpark detail
│       │   ├── Navigate.jsx        # turn-by-turn navigation
│       │   ├── Saved.jsx           # saved / favourite carparks
│       │   ├── SavePrompt.jsx      # prompt to save after viewing
│       │   ├── Rate.jsx            # submit a rating
│       │   └── ThankYou.jsx
│       └── utils/
└── server/                         # Express 5 API
    ├── Dockerfile
    ├── docker-compose.yml          # server (×N) + nginx + redis
    ├── server.js                   # entry point, mounts all routers
    ├── config/
    │   ├── nginx.docker.conf       # Nginx load balancer config (Docker)
    │   ├── nginx.config            # Nginx config for local multi-instance setup
    │   └── redis.js                # Redis client + getCache / setCache helpers
    ├── controllers/
    │   ├── authController.js
    │   ├── carparkController.js
    │   ├── favoriteCarparkController.js
    │   ├── locationController.js
    │   ├── navigateController.js
    │   └── rateCarparkController.js
    ├── data/
    │   └── carparkDB.js            # static HDB carpark dump (SVY21 coords)
    ├── db/
    │   ├── dynamoClient.js         # DynamoDB DocumentClient singleton
    │   ├── dynamoAdapter.js        # get / put / delete wrappers
    │   └── index.js
    ├── helpers/
    │   ├── authHelper.js
    │   ├── carparkHelper.js
    │   ├── coordConverter.js       # SVY21 ↔ lat/lon via proj4 EPSG:3414
    │   ├── favoriteCarparkHelper.js
    │   └── navigateHelper.js
    ├── middlewares/
    │   ├── portMiddleware.js       # injects serving port into responses (load balancing debug)
    │   └── rateLimitMiddleware.ts  # per-IP rate limiter (Redis-backed sliding window, factory: createRateLimiter)
    ├── models/
    │   ├── carpark.js              # entity: toDB / fromDB / toJSON, SVY21 distance
    │   ├── carparkAvailability.js
    │   ├── carparkRating.js
    │   ├── favoriteCarpark.js
    │   ├── location.js
    │   └── user.js
    ├── routes/
    │   ├── authRoute.js            # POST /api/auth/signup|login|logout
    │   ├── carparkRoute.js         # GET /api/carparks
    │   ├── favoriteCarparkRoute.js # GET|POST|DELETE /api/favorites
    │   ├── locationRoute.js        # GET /api/location
    │   ├── navigateRoute.js        # GET /api/navigate/route
    │   └── rateCarparkRoute.js     # GET|POST /api/rating
    ├── services/
    │   ├── authService.js          # Cognito USER_PASSWORD_AUTH flow
    │   ├── carparkService.js       # geocode + filter + availability + rating join
    │   ├── favoriteCarparkService.js
    │   ├── locationService.js
    │   ├── navigateService.js
    │   └── rateCarparkService.js
    └── tests/
        ├── carparkService.test.js
        ├── favoriteCarparkService.test.js
        ├── locationService.test.js
        └── rateCarparkService.test.js

About

ParkPulse SG allows you to locate nearest carparks to your destination, view their real-time slot availability, and navigate to them.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages