| .agents | ||
| apps | ||
| docs | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| docker-compose.yml | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| sampledata.json | ||
| skills-lock.json | ||
MySchools Waitlist Monitor
Private LAN/Coolify-friendly monitor for NYC MySchools waitlist data. The scraper logs in once, fetches the waitlist endpoint, posts the payload to a local Fastify API, and the React dashboard updates over WebSocket.
Apps
apps/scraper: one-shot Node 20 scraper for cron.apps/api: Fastify API and WebSocket broadcaster.apps/web: Vite, React, and Tailwind dashboard.
Install
cp .env.example .env
npm install
Fill .env with your MySchools USERNAME and PASSWORD. Do not commit .env.
Run Locally
npm run dev:api
npm run dev:web
npm run scrape
The API defaults to http://localhost:3001. The dashboard defaults to http://localhost:5173 and connects to ws://localhost:3001/ws.
Cron
The scraper is designed to run once per hour to respect the site's rate limits.
0 * * * * cd /path/to/myschools-waitlist && npm run scrape >> /var/log/myschools-scrape.log 2>&1
Coolify Notes
Run the API and web app as separate services. Keep the scraper as a scheduled job/cron task with the same .env values and API_INGEST_URL pointing at the API service.
Dockerfiles are included for apps/api and apps/web; docker-compose.yml is provided for local container smoke tests. For Coolify, deploy each Dockerfile as its own service and set VITE_WS_URL/VITE_API_URL for the public or LAN API address before building the web image.
API Contract
GET /health: health check.GET /waitlist: current in-memory waitlist state.POST /ingest: scraper payload, either{ data, fetchedAt }or a raw waitlist payload.GET /ws: WebSocket stream. Messages are JSON objects withtype: "state"andpayloadcontaining{ data, fetchedAt, receivedAt }.