10 Commits
Author SHA1 Message Date
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.
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.
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 734bb0a0d2 Initial commit 2024-09-12 15:44:28 -04:00