# FORGE Athletic Club
A full-stack website for a strength-training fitness studio: a public marketing
site with a free-trial booking flow, and an internal admin dashboard to manage
those bookings.
## Stack
- **Runtime:** Node.js (**v22.13+ required**) + Express
- **Database:** SQLite via Node's built-in [`node:sqlite`](https://nodejs.org/api/sq...) module — no native compilation, no build tools, works out of the box on Windows/Mac/Linux
- **Frontend:** HTML + CSS + vanilla JS (no framework) — DOM rendered from the API
- **Auth:** hand-rolled Hamm-SHA256 tokens (no JWT library)
> **Note:** `node:sqlite` is still flagged "experimental" by Node (you'll see a
> one-line warning on startup) — the API itself is stable enough for this
> project and avoids the native-addon build issues that `better-sqlite3` and
> similar packages run into on machines without Python/build tools installed
> (a common problem on Windows).
## Getting started
**On Windows:** right-click the downloaded `.zip` → **Extract All** (Windows
has no built-in `unzip` command). Then, from the extracted folder:
```bash
npm install
npm start
```
**On macOS/Linux:**
```bash
unzip forge-club.zip
cd forge-club
npm install
npm start
```
Requires **Node.js 22.13 or newer** (`node --version` to check). Get it from
[nodejs.org](https://nodejs.org) if needed.
The server reads configuration from `.env`:
```
PORT=3000
ADMIN_PASSWORD=forge-admin-2025
ADMIN_SECRET=<random hex string>
```
Then visit:
- `http://localhost:3000/` — the public site
- `http://localhost:3000/adm...` — the admin dashboard (sign in with `ADMIN_PASSWORD`)
The database file (`forge.sqlite`) is created automatically on first run, with
tables and seed data populated if empty.
## Architecture
```
Client (Browser)
│
▼
Middleware (rate limiter → auth → JSON body parser)
│
▼
Controller (request/response only, no business logic)
│
▼
Service (business logic, validation, SQL)
│
▼
DB (SQLite)
```
```
forge-club/
├── package.json
├── .env
├── public/ ← public site (HTML/CSS/JS)
├── admin/ ← admin dashboard (HTML/CSS/JS)
└── src/
├── index.js ← server entry point
├── app.js ← Express app assembly
├── common/ ← env loader, crypto, tokens, async handler
├── DB/ ← connection, schema, seed
├── middlewares/ ← rate limiter, auth, error handler
└── modules/
├── auth/ ← admin login
├── bookings/ ← public booking + admin booking management
└── content/ ← read-only site content
```
## API
### Public
| Method | Endpoint | Description |
|--------|------------------|-------------------------------------|
| GET | `/api/content` | All site content in one call |
| POST | `/api/bookings` | Create a new booking |
### Admin (Bearer token required)
| Method | Endpoint | Description |
|--------|-------------------------------|---------------------------------|
| POST | `/api/admin/login` | Sign in → `{ token }` |
| GET | `/api/admin/bookings?status=` | List bookings (optional filter) |
| PATCH | `/api/admin/bookings/:id` | Update a booking's status |
| GET | `/api/admin/stats` | Booking counts and totals |
## Security measures
- **Server-side validation** on every booking field (name, phone, goal, program, slot, note) — the client is never trusted.
- **Rate limiting**: 5 booking submissions / 10 minutes and 10 login attempts / 15 minutes, per IP.
- **HMAC-SHA256 signed tokens** with an 8-hour expiry for admin sessions.
- **Constant-time comparison** (`crypto.timingSafeEqual`) for password and token checks, to resist timing attacks.
- **20kb JSON body limit** to guard against oversized payloads.
- **`noindex` header** on `/admin` so the dashboard doesn't appear in search engines.
- **Parameterized SQL** everywhere — no string-built queries.
- **Uniform error responses** — no internal error details leak to the client.
## Future work
- Booking confirmation emails (Nodemailer + SMTP)
- Member login with JWT refresh tokens
- Full admin CRUD for programs, coaches, and stories
- Coach photo uploads (Multer)
- Google OAuth login
- VPS deployment behind Nginx + Certbot (HTTPS)