Files
SixFlagsSuperCalendar/README.md
T
josh f87462385c
Build and Deploy / Lint, typecheck, test (push) Successful in 34s
Build and Deploy / Build & Push (push) Successful in 1m6s
docs: sync README and docs/ with current codebase
Surfaces features that landed after the last big docs pass: per-ride
history pages, Fast Lane wait times, outage shading on the today chart,
Tier-5 wait-time sampler, production-hardening pieces (rate limiter,
structured logger, env validation, graceful shutdown), and the new
rides + ride_wait_samples tables. Also corrects the weather-delay rule
to match the "open" vs "closing" gate now in rides.ts.
2026-06-02 15:31:50 -04:00

189 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Thoosie Calendar
A week-by-week calendar showing operating hours for all Six Flags Entertainment Group theme parks — including the former Cedar Fair parks. Data is fetched from the Six Flags internal API via a backend service and stored in SQLite. Click any park to see its full month calendar and live ride status with current wait times.
## Parks
24 theme parks across the US, Canada, and Mexico, grouped by region:
| Region | Parks |
|--------|-------|
| **Northeast** | Great Adventure (NJ), New England (MA), Great Escape (NY), Darien Lake (NY), Dorney Park (PA), Canada's Wonderland (ON) |
| **Southeast** | Over Georgia, Carowinds (NC), Kings Dominion (VA) |
| **Midwest** | Great America (IL), St. Louis (MO), Cedar Point (OH), Kings Island (OH), Valleyfair (MN), Worlds of Fun (MO), Michigan's Adventure (MI) |
| **Texas & South** | Over Texas, Fiesta Texas (TX), Frontier City (OK) |
| **West & International** | Magic Mountain (CA), Discovery Kingdom (CA), Knott's Berry Farm (CA), California's Great America (CA), Mexico |
## Documentation
Detailed docs live in the [`docs/`](docs/) folder:
- [**Architecture**](docs/ARCHITECTURE.md) -- system design, data flow, caching layers, database schema, external APIs
- [**Operations**](docs/OPERATIONS.md) -- deployment, monitoring, troubleshooting, backup, scheduler management
- [**API Reference**](docs/API.md) -- complete backend endpoint documentation with request/response examples
- [**Development**](docs/DEVELOPMENT.md) -- local setup, project structure, adding parks, testing, code conventions
## Architecture
The app runs as two containers:
| Container | Port | Purpose |
|-----------|------|---------|
| **web** | 3000 | Next.js frontend — pure presentation layer, fetches all data from the backend API |
| **backend** | 3001 | Hono API server — owns the SQLite database, runs tiered cron scheduling, handles all external API calls |
The frontend makes no direct database or external API calls. All data flows through the backend.
## Tech Stack
- **Next.js 15** — App Router, Server Components, standalone output
- **Tailwind CSS v4** — `@theme {}` CSS variables, no config file
- **Hono** — lightweight TypeScript API framework for the backend
- **SQLite** via `better-sqlite3` — owned exclusively by the backend
- **node-cron** — tiered scheduling (hourly → daily) for data freshness
- **Six Flags CloudFront API** — park operating hours and ride schedules
- **Queue-Times.com API** — live ride open/closed status and wait times
## Ride Status
The park detail page shows ride open/closed status using a two-tier approach:
1. **Live data (Queue-Times.com)** — when a park is operating, ride status and wait times are fetched from the [Queue-Times.com API](https://queue-times.com/en-US/pages/api) and cached for 5 minutes. All 24 parks are mapped. Displays a **Live** badge with per-ride wait times.
2. **Schedule fallback (Six Flags API)** — when Queue-Times data is unavailable, the app falls back to the nearest upcoming date from the Six Flags schedule API as an approximation.
### Fast Lane wait times
A second wait number is fetched from Six Flags' `/wait-times/park/{apiId}` endpoint and joined onto each ride by name. The park page has a **Fast Lane** toggle (persisted in `localStorage.fastLaneMode`) that swaps the displayed wait between regular and Fast Lane. On the today chart, Fast Lane appears as a second line.
### Per-ride history
Click any ride name on a park page to open `/park/[id]/ride/[slug]` — a detail page with three tabs:
- **Today** — 5-minute wait-time samples (regular + Fast Lane) with outage markers
- **7 days** — daily average / max wait and uptime percentage
- **30 days** — same aggregates over a longer window
Samples are stored in the `ride_wait_samples` table by a Tier-5 cron job that runs every 5 minutes for parks currently within their operating window. Contiguous "ride closed during park hours" runs are shaded on the today chart with a `#N — Hh Mm` label.
### Roller Coaster Filter
When live data is shown, a **Coasters only** toggle filters to roller coasters. Coaster lists are hardcoded in `lib/coaster-data.ts`.
## Data Refresh
The backend runs a tiered scraping schedule via node-cron:
| Tier | Schedule | Scope |
|------|----------|-------|
| 1 | Hourly (MarDec) | Today's hours for all parks |
| 2 | Every 6 hours | Current month for all parks |
| 3 | Twice daily (3 AM, 3 PM) | Current + next month |
| 4 | Daily at 3 AM | Full year (respects 72h staleness window) |
| 5 | Every 5 minutes | Wait-time samples for all currently-open parks (writes `ride_wait_samples`) |
Past dates are never overwritten. The hourly tier compares live data against the database before writing — unchanged data is skipped. Each tier has its own concurrency latch — if a tick is still running when the next would fire, the new tick is skipped and logged rather than stacked.
A manual trigger is available via the backend API:
```bash
curl -X POST http://localhost:3001/api/scrape/trigger?scope=today
# scope: today | month | upcoming | full | force
```
---
## Local Development
**Prerequisites:** Node.js 22+, npm
```bash
# Install frontend dependencies
npm install
# Install backend dependencies
cd backend && npm install && cd ..
```
### Start the backend
```bash
cd backend
npm run dev
```
The backend starts on port 3001, initializes the database, and begins the cron schedule. On first run it creates an empty database — the schedulers will populate it automatically, or trigger a manual scrape.
### Start the frontend
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000). Navigate weeks with the `←` / `→` buttons (or arrow keys); your selected week persists across visits via the `tcWeek` cookie. Click any park name to open its detail page.
### Debug a specific park + date
Inspect raw API data and parsed output for any park and date:
```bash
npm run debug -- --park kingsisland --date 2026-06-15
```
### Run tests
```bash
npm test
```
---
## Deployment
The app ships as two Docker images:
```bash
docker compose up -d
```
Images are built and pushed automatically by CI on every push to `main`.
### Environment variables
See [`.env.example`](.env.example) for the full list and defaults.
**web:**
| Variable | Default | Description |
|----------|---------|-------------|
| `BACKEND_URL` | _(required in prod)_ | Backend API base URL. Throws at startup if unset when `NODE_ENV=production`. |
| `NEXT_PUBLIC_PLAUSIBLE_SRC` | — | Plausible script URL. Analytics only render when both this and the website ID are set. |
| `NEXT_PUBLIC_PLAUSIBLE_WEBSITE_ID` | — | Plausible website ID. |
**backend:**
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `3001` | Port the Hono server listens on. |
| `TZ` | `UTC` | Timezone for cron schedules (e.g. `America/New_York`). |
| `PARK_HOURS_STALENESS_HOURS` | `72` | Hours before park schedule data is re-fetched. |
| `RATE_LIMIT_PER_MIN` | `60` | Per-IP request limit for the public API, per minute. Enforced by `backend/src/middleware/rate-limit.ts`; over-limit requests get a `429` with a `Retry-After` header. |
### Updating
```bash
docker compose pull && docker compose up -d
```
### Backend API endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /api/calendar/week?start=YYYY-MM-DD` | Week calendar for all parks |
| `GET /api/calendar/:parkId/month?month=YYYY-MM` | Month calendar for one park |
| `GET /api/parks/:id/rides` | Live rides or schedule fallback |
| `GET /api/parks/:id/rides/:slug` | Per-ride detail + today/7d/30d wait-time history |
| `GET /api/parks` | Park list with metadata |
| `GET /api/status` | Health check, scrape timestamps, DB stats |
| `POST /api/scrape/trigger?scope=...` | Manual scrape trigger |