A Dockerized web application that aggregates live occupancy data for Fitness Platinium clubs.
- This project is entirely unaffiliated with Fitness Platinum. It is not authorized, endorsed, sponsored, or approved by Fitness Platinum in any form. All related trademarks are the property of their respective owners.
- This software is provided for educational and research purposes only. It may be incomplete, unreliable, or non-functional. By using it, you accept full responsibility for any consequences and agree to indemnify the creator, contributors, and all other involved parties from any and all liability.
- This project relies on unofficial and private APIs that may change, break, or be withdrawn at any time without notice. Use is entirely at your own risk.
- Real-time occupancy tracking
- Historical data analysis and predictions
- Interactive dashboard with charts
- Geolocation-based club sorting
- SQLite persistence
- Automated polling every 10 minutes
- RESTful API
- Node.js 20+
- npm or pnpm
# Install dependencies for both frontend and backend
cd backend && npm install
cd ../frontend && npm install# Build the project
npm run build
# Start the server (from backend directory)
cd backend
npm startThe server will start on http://localhost:8080
# Build image
docker build -t platinum-pulse .
# Run container
docker run -p 8080:8080 -v $(pwd)/data:/data --env-file .env platinum-pulseOr use docker-compose:
docker-compose upVisit http://localhost:8080
Copy .env.example to .env and configure:
PG_API_BASE_URL=https://goapi2.perfectgym.com
PG_API_TOKEN=your-bearer-token-here
COMPANY_ID=302
POLL_INTERVAL_MINUTES=10
CLUB_REFRESH_CRON=0 3 * * *
PORT=8080
DATABASE_URL=file:../data/platinum-pulse.sqliteplatinum-pulse/
├── backend/ # Fastify API server
│ ├── src/
│ │ ├── routes/ # API routes
│ │ ├── services/ # Business logic
│ │ ├── config/ # Configuration
│ │ └── main.ts # Entry point
│ └── prisma/ # Database schema
├── frontend/ # React + Vite frontend
│ └── src/
│ ├── components/ # UI components
│ ├── pages/ # Page components
│ ├── hooks/ # Custom hooks
│ └── api/ # API client
├── data/ # SQLite database (volume mount)
└── Dockerfile # Production container
GET /api/health- Health check with database statusGET /api/stats- System statistics (clubs, snapshots, last poll)GET /api/clubs?lat=X&lng=Y- List all clubs with current occupancy (optional geolocation)GET /api/clubs/:id- Get specific club detailsGET /api/occupancy/:clubId- Get occupancy data with predictionsGET /api/occupancy/:clubId/history?days=7- Get historical occupancyPOST /api/poll- Manually trigger occupancy pollPOST /api/sync-clubs- Manually trigger club metadata sync
- Backend: Node.js, Fastify, Prisma, SQLite
- Frontend: React 18, TypeScript, Vite, TanStack Query, Recharts, Tailwind CSS
- Scheduling: node-cron
- HTTP Client: axios with retry logic
- Logging: pino
# Build everything
npm run build
# Lint code
npm run lint
# Run tests
npm run test
# Development mode (backend with watch)
cd backend
npm run devcd backend
npx prisma migrate dev --name migration_name
npx prisma generateThe application is designed to run in a single Docker container suitable for Synology NAS or similar environments.
- Build the Docker image
- Mount a volume to
/datafor database persistence - Provide environment variables via
.envor docker-compose - Expose port 8080
- The application polls the PerfectGym API every 10 minutes by default
- Club metadata is refreshed nightly at 3 AM (configurable via cron)
- Historical predictions use a 7-day rolling average
- The frontend requests geolocation once per session for distance-based sorting
- All data is stored locally in SQLite - no external services required