Files

93 lines
4.1 KiB
Markdown
Raw Permalink 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.
# unloze playtime display
Next.js dashboard for the `unloze_playtimestats` database. Runs as a single
Node process; nginx reverse-proxies a subdomain to it.
## Stack
- Next.js 14 (App Router) + TypeScript
- Tailwind CSS
- Recharts for graphs
- `mysql2` connecting directly to MySQL (server-side only, in API routes —
the browser never sees DB credentials)
## Pages built so far
- `/` — Overview: concurrent-player population graph (hand-rolled SVG, not
a charting library — scroll to zoom, drag to pan all the way back to
2015-07-16, click any point for who was online then), a map-to-map
population bar chart (same zoom/pan, pool of 150 maps), and a "recurring
window" chart for questions like "who's online 615h every Tuesday"
(pick days of week + time window + date range)
- `/players` — search players by current/previous name or SteamID (shows
which old name matched, if any), sortable by recent activity, name, or
highest playtime
- `/players/[steamid]` — avatar, previous names, SteamID, Steam profile link,
RaceTimer rank/level + profile link, total playtime, and a session-by-session
history (with the map(s) each session overlapped)
- `/maps` — search maps by name
- `/maps/[mapname]` — every time period that map was played
- `/maps/period/[mapId]` — one specific occurrence: players present at any
point during it, votes cast (grouped by vote event, since a map could
theoretically see more than one), and prev/next map navigation
- `/countries` — every country with at least one player, sorted by player
count descending
- `/countries/[code]` — players from that country, sortable by highest
playtime or most recently played
A `favicon.ico` in `app/` is picked up automatically by Next — no config needed.
Every feature from the original planning conversation is now built.
## Deploying on the Hetzner box
```bash
npm install
npm run build
pm2 start ecosystem.config.js
pm2 save # persist across reboots
pm2 startup # follow the printed instructions once, to enable on boot
```
Then point nginx at it — see `nginx.example.conf` for a working server block
(adjust the domain and cert paths). After linking it into
`/etc/nginx/sites-enabled/`, reload nginx to pick it up.
## Redeploying
```
rm -rf node_modules .next
npm install
npm run build
pm2 restart unloze-playtime-display
```
## Notes
- `session_end_dt` is refreshed roughly every 60s by the plugin as a
heartbeat while a player is connected (not just on disconnect) — see
`lib/db.ts` usage in `/api/population`, which treats a `NULL` or
recent `session_end_dt` as "still active."
- Running on **Next.js 15**. One thing to remember when building the
`/players/[steamid]` and `/maps/[mapname]` dynamic pages next: Next 15
made the `params` (and `searchParams`) prop passed to pages/route handlers
a `Promise` you need to `await`, e.g.
`export default async function Page({ params }: { params: Promise<{ steamid: string }> }) { const { steamid } = await params; ... }`.
Doesn't affect anything currently built (no dynamic segments yet), but
will the moment those pages get added.
- SteamIDs are stored in Steam2 format (`STEAM_0:1:12345678`) to match the
existing `player_time` table. `lib/steam.ts` converts to Steam64 on the
fly for profile links/avatars — nothing needs to be stored pre-converted.
- **Timezone**: `ecosystem.config.js` pins `TZ=Europe/Berlin`, confirmed
against `SELECT @@global.time_zone, @@session.time_zone, NOW();` on the
actual server (MySQL runs `SYSTEM`, which resolves to German local time).
All the datetime parsing (`lib/dates.ts`, `/api/population`) reads MySQL
`DATETIME` strings with no timezone info attached, so Node's own local
timezone determines how they're interpreted — it must match MySQL's, or
"how many players are online right now" ends up quietly wrong (this was
a real bug, not hypothetical — it undercounted the live player count by
roughly half before this fix). If the DB server ever moves or MySQL's
timezone changes, update this value to match. After changing it,
`pm2 restart ecosystem.config.js --update-env` is required — a plain
`pm2 restart <name>` won't pick up the new env var.