Files
projects-jenz/RaceTimer_2026/racetimer_endpoints/README.md
T

108 lines
7.8 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.
# Racetimer backend (2026)
REST API for the UNLOZE racetimer, reading the `unloze_racetimer_css_2026` database.
Java 8+, Jersey (JAX-RS 2.1), deployed as `racetimer_endpoints-1.0.war` on Tomcat 8.5, the same as before.
All paths below are under `https://<backend>/racetimer_endpoints-1.0/api/`.
## Deploying
1. Build: `mvn package`. The output is `target/racetimer_endpoints-1.0.war`.
2. Update `/opt/tomcat/race_backend_settings.json`. It is the same file the old backend used, with more keys; see `race_backend_settings.example.json`.
- `racetimerURL` must now point at the **`unloze_racetimer_css_2026`** database.
- Add the `sourcebans*`, `gidStaff` and `gidAdmin` keys. They work like `SBPP_DB_*`, `GID_STAFF` and `GID_ADMIN` in the entwatchbans panel.
- Set `jwtSecret` to at least 32 random characters. Without it, admins are signed out on every Tomcat restart.
- Set `publicBackendUrl` to this backend's public URL.
- Set `frontendUrl` to the React site's URL. Steam sends admins back there after they sign in.
- Move the Steam Web API key into `steamApiKey`. The old code had it hardcoded in `Facade.java`, which shipped in the repo, so generate a new key.
3. Deploy the WAR as before. The data loads on startup and reloads every `refreshMinutes` (default 30).
The settings path can also be given with `-Dracetimer.settings=/path/file.json` or the `RACETIMER_SETTINGS` environment variable.
## Points
Points are calculated per category, from each player's **best valid** time only. Tied times share a position.
| Rule | Detail |
|---|---|
| Who gets points | Everyone while a board has fewer than 200 completions. From 200 on, only the faster half (200 → top 100, 210 → top 105). |
| Base points | n − (position − 1): the fastest gets n (the number of completions), each position below gets one less. The slower half gets 0, so on big boards points drop from about n/2 straight to 0 at the cut. |
| Bonus multiplier | Only on boards with 100+ completions. 4× at #1, sliding smoothly to 2× at the top-1% mark, then to 1× at the top-5% mark. |
| CLASSIC RACETIMER | Final points ÷ 10. |
| Invalid category | Still shown with positions, but gives 0 points. The admin and time are public (`invalidation`). |
| Invalid record | Still listed on the board in time order with `isInvalid: true`, position 0 and 0 points. Only that one run is removed from the ranking; the player's previous valid time counts again. The admin and time are public (`invalidation`). |
| Servers | ZE1 and ZE2 points and ranks are separate. Categories from any other tag (e.g. `dev`) are hidden. |
The old flat +2500 bonus for small boards is gone.
`GET` responses are computed from an in-memory snapshot. Admin changes rebuild it immediately.
## In-game plugins
`racetimer_rank.sp` and `toplvl.sp` keep working unchanged. `player/{steamid}` and `leaderboard/minified/{offset}` still return `PlayerPoints` (and `name`).
Without `?server=`, both endpoints use `defaultServerTag` (ze1). On ZE2, add `?server=ze2` to those two URLs so levels come from ZE2 points.
Levels drop for CLASSIC data, as intended.
## Who invalidated what
Run `racetimer_invalidation_audit.sql` once on the racetimer database. It adds `invalidated_by_steam`, `invalidated_by_name` and `invalidated_at` to `timer_records` and `zone_categories`.
- Invalidating stores the signed-in admin's SteamID, name and the current time. Invalidating something that is already invalid keeps the first admin and time.
- Restoring sets `is_invalid = 0` and clears the three columns.
- Every invalid run and category in the API has `invalidation: {steamID, steamID64, name, at}` (`at` in Unix seconds). Player rows and history also have `categoryInvalidation`. Rows flagged before the columns existed have all fields `null`.
- Until the script is run, the site keeps working (a warning is logged) but cannot say who invalidated what, and the admin buttons return an error asking for the script.
## Endpoints
`{steamid}` accepts `STEAM_0:x:y`, `STEAM_1:x:y`, `[U:1:n]` or a SteamID64.
`?server=` is `ze1` or `ze2` and defaults to ze1.
Errors are JSON: `{"statusCode": 404, "errorMessage": "..."}`.
### Public
| Method & path | Returns |
|---|---|
| `GET timers/leaderboard/{offset}?server=` | 100 players ranked by that server's points. Fields: `steamID`, `steamID64`, `name`, `Avatar`, `Rank`, `PlayerPoints`, `Times`, `UrlBanners`, `server`, `servers` (`{"ze1": {points, rank, times}, "ze2": {...}}`), `badges`. |
| `GET timers/leaderboard/minified/{offset}?server=` | `[{name, PlayerPoints}]` (for `toplvl.sp`) |
| `GET timers/player/{steamid}?server=` | One player, same fields as the leaderboard. 404 if unknown. |
| `GET timers/player/badges/{steamid}` | `{badgesUrls, badges: [{name, url}]}` |
| `GET timers/player/maps/{steamid}/{offset}?server=` | 50 rows of the player's best per category, plus any faster invalidated run (`isInvalid: true`, position 0). Fields: `recordId`, `categoryId`, `mapName`, `stage`, `categoryNumber`, `serverTag`, `isLegacy`, `categoryInvalid`, `isInvalid`, `time`, `position`, `completions`, `bonusMultiplier`, `points`, `recordedAt`. Without `server`, both servers are included. |
| `GET timers/player/history/{steamid}/{offset}?categoryId=` | 50 improvements, newest first. Fields: `time`, `previousTime`, `improvedBy` (seconds), `recordedAt`, `isInvalid`, `isCurrentBest`, plus the category fields. Legacy records are last, with `recordedAt: null`. |
| `GET timers/allmaps` | `[{mapName, allCategoriesInvalid, stages: [{stage, allCategoriesInvalid, categories: [category]}]}]` |
| `GET timers/map/{mapname}` | One map in the same shape (case-insensitive). |
| `GET timers/category/{id}/{offset}` | `{category, offset, pageSize, entries: [75], invalidated: [...]}`. `entries` holds valid times and invalidated runs together, fastest first; invalidated ones have `isInvalid: true`, `position: 0`, `points: 0`. `invalidated` repeats just those runs. |
| `GET timers/searchplayers/{text}?server=` | Up to 100 players, matched on name or Steam ID. |
| `GET timers/searchmaps/{text}` | Maps whose name contains the text. |
`category` fields: `id`, `categoryNumber` (the same "Category N" as in-game), `mapName`, `stage`, `serverTag`, `serverCvars`, `cvars` (`[{name, value}]`, for showing what differs between categories), `isLegacy`, `isInvalid`, `givesPoints`, `completions`, `fastestTime`.
Board `entries` fields: `recordId`, `position`, `steamID`, `steamID64`, `name`, `avatar`, `badgesUrls`, `time`, `points`, `bonusMultiplier`, `recordedAt` (Unix seconds, `null` for legacy), `isLegacy`.
### Admin sign-in (Steam)
1. The site links the admin to `GET auth/steam/login`.
2. After Steam, the backend redirects to `frontendUrl` with either:
- `#token=<token>`
- or `#loginError=not_admin`, `#loginError=steam_verification_failed`, or `#loginError=admin_check_unavailable`
3. The site sends the token as `Authorization: Bearer <token>`. The old `x-access-token` header also works.
4. `GET auth/me` returns `{steamID, steamID64, name, role, expiresAt}`.
- The role is `admin` for `gidAdmin` groups and `staff` for `gidStaff` groups.
- A missing or expired token gives 401.
### Admin actions (staff and admin)
| Method & path | Body |
|---|---|
| `PUT admin/records/{recordId}` | `{"invalid": true}` or `{"invalid": false}` |
| `PUT admin/categories/{categoryId}` | same |
The response is `{id, invalid, changedBy}`. The schema has no audit table, so every change is written to the Tomcat log with the admin's name and Steam ID.
## Removed endpoints
`timers/mapsizecache/...` and the old `timers/map/{mapname}/{stage}/{offset}` are gone. Use `category.completions` and `timers/category/{id}/{offset}`.
The old username/password `login` endpoint and the JPA entities are gone too.
## Tests
`mvn test` runs the tests for the points formula, the snapshot builder, history, Steam IDs, tokens, Steam OpenID checks, and the whole API in memory. The API tests use no database.