17 Commits
Author SHA1 Message Date
john-okeefe ee7aa9921b fix(adminGames): apply Steam URL normalization and scraper guard to create/update
Release / build-and-push (push) Successful in 1m1s
Admin create had no URL handling at all (and crashed on missing steamId
via .length), and admin update passed req.body straight to the scraper.
Reuse normalizeSteamId in both, 400 on unrecognized input, and reject
failed scrapes (non-object / missing title+frontImage) before
Game.create/findOneAndUpdate so failures report as 'Steam lookup failed'
instead of schema validation noise.
v1.2
2026-09-06 10:11:07 -04:00
john-okeefe 964415198a fix(games): persist normalized Steam ID and guard scraper failures in create()
The store-URL support extracted the app ID into a local but never wrote
it back, so steamScraper still received the full URL, keyed the Steam API
response by the URL string, threw, and its caught Error object flowed into
Game.create() — surfacing as misleading 'Path lastModifiedBy / createdBy /
frontImage / title is required' validation errors.

- add utils/normalizeSteamId: digits pass through, /app/<id>/ (plus
  /agecheck/app/<id>/ and query strings) extracts the ID, empty -> '',
  anything else -> null (no .match()[0] / .includes crash on bad input)
- create(): 400 cleanly on unrecognized input, write the normalized ID
  back to req.body.steamId so dup checks, scraper, and stored doc agree
- guard the scrape result (missing title/frontImage, false, or Error) and
  400 'Steam lookup failed' instead of leaking it into Game.create
2026-09-06 10:11:07 -04:00
john-okeefe 0adf0bd51f fix(admin): await user.save() without callback in createAdmin
Release / build-and-push (push) Successful in 1m14s
Mongoose 7 removed callback support on Model.prototype.save(),
so the seeder threw 'no longer accepts a callback' on every
boot (caught and logged, non-fatal, but noisy and the success
path never ran). Await the promise and log on success.
v1.1
2026-09-05 22:48:53 -04:00
john-okeefe 39f1c88255 fix(ratelimit): split global and login budgets so storms can't lock out login
One shared 100 req / 10 min limiter counted everything —
401s, CORS preflights, even /health — so a bad-token burst
from a second frontend burned the budget and 429'd POST
/api/auth/login too, leaving no way back in short of
waiting out the window or restarting (MemoryStore reset).

New middleware/rateLimit.js holds two limiters, verified
against express-rate-limit@5.5.1:

- apiLimiter (300 / 15 min) skips OPTIONS, /health, and the
  public auth endpoints, so preflights, probes, and login
  attempts never drain real API budget.
- authLimiter (20 / 15 min, successful logins free) guards
  register/login/forgotpassword/resetpassword against
  brute force. 429 there means 20 wrong passwords, never
  a busy API.

Storm-tested with live server: 320 bad-token hits burn the
global budget yet login still returns 200; 25 bad logins
trip only the login limiter while API traffic is untouched.
2026-09-05 22:48:48 -04:00
john-okeefe 4fe5aa25f8 chore(ci): tag-triggered release workflow and helpers
Release / build-and-push (push) Successful in 1m17s
Add .gitea/workflows/release.yml: on v* tag push (or manual
dispatch) build the Docker image and push
git.linuxhg.com/games-database/games-api:<tag> plus :latest,
then create/update the Gitea Release from the annotated tag
message. Main-branch pushes do nothing so WIP never ships.

Add cliff.toml (conventional-commit grouping for release
notes), Makefile release target (cliff notes into annotated
tag, then push), and ./release wrapper (accepts 1.0 or
v1.0). Release flow: ./release v1.0, matching bookhoard.

Requires a REGISTRY_TOKEN repo Actions secret (PAT with
write:package and write:repository); Gitea's auto token
lacks package scope.
v1.0
2026-09-05 21:05:20 -04:00
john-okeefe a5ceb721f3 chore(docker): portable image and registry run template
Dockerfile: stop baking secrets into the image. Only NODE_ENV
remains; all runtime config (MONGO_URI, SMTP_*, token
secrets) comes from the environment so one image runs
anywhere. Install curl for container healthchecks. App
variable names untouched (singular JWT_EXPIRE as read by
models/User.js).

docker-compose.example.yml: rewrite as a prod run template
for git.linuxhg.com/games-database/games-api (IMAGE_TAG,
default latest) with restart, env_file .env, and a curl
/health healthcheck. Copy to docker-compose.yml next to
the server .env, then pull and up. Full variable list kept
commented out so values can live in the file instead of
.env if preferred.
2026-09-05 20:37:03 -04:00
john-okeefe 38e6d21585 feat(health): add unauthenticated GET /health endpoint
Returns 200 { status: 'ok' } for container healthchecks and
monitoring. Placed before all /api/* routes so it bypasses
auth; intentionally static with no DB dependency so the
container reports healthy whenever the process is serving.
2026-09-05 20:36:57 -04:00
john-okeefe 689610b434 chore(compose): sync example env with codebase
Add NODE_ENV (used by config/db.js and controllers/auth.js
to switch MONGO_URI/MONGO_DEV_URI and secure cookies).

Drop PATH, BUN_RUNTIME_TRANSPILER_CACHE_PATH, and
BUN_INSTALL_BIN leaked from container runtime env; they are
not read by the app.

Drop JWT_EXPIRES duplicate; app reads JWT_EXPIRE
(models/User.js expiresIn), matching .env.

Restore SECURE=false default (used by utils/sendEmail.js
via yn(Bun.env.SECURE)).

Keep list aligned with Dockerfile and .env keys so the
example stays a valid template for server deployments.
2026-09-05 20:01:30 -04:00
john-okeefe 748616df9d feat(games): accept full Steam store URL in create()
Allow callers to pass either a raw Steam app ID or a full store URL (e.g. https://store.steampowered.com/app/730/...) in req.body.steamId.

In create() in controllers/games.js:

- normalize input into a local steamId variable: if the value includes store.steampowered.com, extract the numeric ID via match(/\d+/)[0], otherwise use the value as-is

- use the normalized steamId for both duplicate checks: user-scoped lookup (steamId + accessedBy.user) and global lookup

This lets users paste a copied store link directly without manually stripping the app ID, while keeping existing raw-ID behavior unchanged.
2026-09-05 19:16:14 -04:00
john-okeefe a9b9fe1047 style(games): reformat controllers/games.js with Prettier
Apply Prettier default formatting across the games controller with no logic changes:

- single -> double quotes, add missing semicolons

- 2-space indentation and consistent line wrapping

- expand dense ternary/decode blocks in create() and update() for readability

This isolates the upcoming create() Steam URL feature so its functional diff stays small and reviewable.
2026-09-05 19:16:06 -04:00
john-okeefe 20c1fd7be9 Ensure API Response is in English
added English to the API URL to ensure the returned JSON is in English. I'm leaving the Headers language in place, but it is not as reliable
2025-06-29 11:50:29 -04:00
john-okeefe 2800d2fb2e sorting now default starting with most recently created 2024-10-04 23:35:00 -04:00
john-okeefe cce705c4f7 for now, cors supports all 2024-09-12 19:33:14 -04:00
john-okeefe ed889d95e6 added docker support 2024-09-12 19:32:45 -04:00
john-okeefe c61d0c40af added docker support 2024-09-12 19:32:09 -04:00
john-okeefe d575a4efc5 moved api from monorepo 2024-09-12 15:48:27 -04:00
john-okeefe 734bb0a0d2 Initial commit 2024-09-12 15:44:28 -04:00