Files
newbury-nights/README.md
T

145 lines
6.6 KiB
Markdown

# Newbury Nights
A fan-made AR ghost-hunting web app — a tribute to the AR mechanics of the LEGO® Hidden Side™ app. The focus is the **Hunter Mode** AR loop: raise your phone as a ghost detector, scan the colour wheel to uncover gloom, lock on, and blast ghosts down before your battery dies.
> Fan-made tribute. Not affiliated with, sponsored by, or endorsed by the LEGO Group. LEGO® and Hidden Side™ are trademarks of the LEGO Group.
## What's inside
- **Backend** — Node + Express 5, SQLite via `better-sqlite3`, bcrypt password hashing, JWT auth (8h), multer image uploads.
- **Frontend** — Three.js (ES module import maps via CDN), `getUserMedia` camera passthrough with `DeviceOrientation` gyro look (iOS Safari friendly), `BarcodeDetector` QR scanning with a manual code fallback.
- **Ghost rendering** — animated-GIF billboards (texture pumped via `texture.needsUpdate` each frame) when a ghost has an uploaded image; procedural Three.js wisp meshes otherwise.
- **Spawning** — rarity-weighted: ★ common down to ★★★★ legendary.
- **Admin panel** (`/admin`) — JWT-gated. Upload / enable / disable / delete ghost images, create sets with scan codes linked to ghost rosters (many-to-many via `set_ghosts`).
- **Public endpoint** — `GET /api/scan/:code` requires no auth and returns a set's ghost roster.
## Data
The ghost roster, stats, abilities, and boss→set references are seeded from `data/*.json`, which are generated from the source spreadsheet by `scripts/extract_ghosts.py`. Three ghost types (red / yellow / blue) and four rarity tiers drive the colour-wheel and damage mechanics.
To regenerate the JSON from a spreadsheet:
```bash
python3 scripts/extract_ghosts.py path/to/Ghost_Data.xlsx data
```
## Step-by-step setup
This walks through a fresh deploy on the Ubuntu server, behind the existing nginx (which already terminates HTTPS).
### 1. Prerequisites
Node 18 or newer, plus the build toolchain so `better-sqlite3` can compile its native module:
```bash
node --version # expect v18+; install via nvm or nodesource if missing
sudo apt-get update
sudo apt-get install -y build-essential python3
```
`build-essential` is required — without it `npm install` fails while compiling `better-sqlite3`.
### 2. Get the code and install dependencies
```bash
git clone https://gitea.hideawaygaming.com.au/jessikitty/newbury-nights.git
cd newbury-nights
npm install
```
`npm install` compiles the native SQLite module, so it may take a minute on first run.
### 3. Configure environment
```bash
cp .env.example .env
```
`.env` is loaded automatically at startup via dotenv, so `npm start` and `npm run seed` read it directly — no process manager or manual `export` needed. Edit `.env` and set, at minimum:
- `JWT_SECRET` — a long random string (e.g. `openssl rand -hex 32`). This signs login tokens; treat it like a password.
- `ADMIN_USER` / `ADMIN_PASS` — the bootstrap admin login created on first seed. You'll change the password from the admin panel right after.
- `PORT` — defaults to `33033`; change if that port is taken. Keep it in sync with the nginx upstream (step 6).
### 4. Seed the database
```bash
npm run seed
```
This creates `db/newbury.sqlite`, loads the 111 ghosts / 36 abilities / 17 sets from `data/*.json`, and creates the admin user. Expected output ends with something like `Seed complete: { ghosts: 111, abilities: 36, sets: 17, rosterLinks: 153 }`.
Re-running `npm run seed` is safe — it only seeds ghost/set data when the `ghosts` table is empty, so it never clobbers admin edits. It always ensures an admin user exists.
### 5. Start the app
```bash
npm start
```
You should see `Newbury Nights listening on http://127.0.0.1:33033` (or whatever `PORT` you set). Test it locally:
```bash
curl http://127.0.0.1:33033/healthz # -> {"ok":true}
curl http://127.0.0.1:33033/api/scan/NN-70419 # -> Wrecked Shrimp Boat roster + Captain Archibald
```
For production, run it under a process manager so it restarts on reboot/crash — e.g. `pm2 start server.js --name newbury-nights`, or a systemd unit. dotenv still loads `.env` under either.
### 6. Put nginx in front (HTTPS)
Camera and gyro APIs only work in a secure context, so the site must be served over HTTPS. Copy the example server block and adjust `server_name` and certificate paths (the example upstream already points at `127.0.0.1:33033` — match it to your `PORT`):
```bash
sudo cp deploy/nginx.conf.example /etc/nginx/sites-available/newbury-nights
# edit server_name + ssl_certificate paths to match your domain/certs
sudo ln -s /etc/nginx/sites-available/newbury-nights /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
The app runs plain HTTP on `127.0.0.1:PORT`; nginx terminates TLS and forwards to it. `app.set('trust proxy', 1)` is already set so secure cookies work behind the proxy.
### 7. First login and lock-down
1. Open `https://your-domain/admin`.
2. Sign in with the `ADMIN_USER` / `ADMIN_PASS` from your `.env`.
3. Go to the **Account** tab and change the password immediately.
4. Under **Ghosts**, upload billboard GIFs and enable/disable ghosts. Under **Sets**, edit scan codes and rosters.
### 8. Play
Open `https://your-domain/` on a phone. Use **Scan a Set** (camera QR or type a code like `NN-70419`), **Free Hunt** for a quick random session, or **Ghost Index** to browse the roster. Allow camera and motion access when prompted.
## nginx
See `deploy/nginx.conf.example` for the full reverse-proxy block referenced in step 6.
## API quick reference
Public:
- `GET /api/scan/:code` — set roster + boss for a scan code
- `GET /api/freehunt?n=&type=` — rarity-weighted random spawns
- `GET /api/ghosts?type=&rarity=&boss=` — public ghost index (enabled only)
- `GET /api/abilities` — ability reference
Auth:
- `POST /auth/login` · `POST /auth/logout` · `GET /auth/me` · `POST /auth/change-password`
Admin (JWT required):
- `GET/POST /api/admin/ghosts`, `PATCH/DELETE /api/admin/ghosts/:id`, `POST /api/admin/ghosts/:id/image`
- `GET/POST /api/admin/sets`, `PATCH/DELETE /api/admin/sets/:id`, `PUT /api/admin/sets/:id/roster`
## Project layout
```
server.js Express app + static hosting
db/index.js SQLite schema + connection
routes/ auth, public api, admin api, auth middleware
scripts/extract_ghosts.py xlsx -> data/*.json
scripts/seed.js seed DB from data/*.json + bootstrap admin
data/ ghosts.json, abilities.json, sets.json
public/ index.html (game), admin.html, css/, js/
deploy/ nginx.conf.example
uploads/ uploaded ghost billboards (gitignored)
```