You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Summary
- **Restore Nest.js DI**: replaced `tsx watch` with `tsc -b --watch` +
`node --watch` in the server's dev script — tsx 4.x/esbuild silently
drops `emitDecoratorMetadata`, breaking all constructor injection so
every service injected as `undefined`
- **Build-time tfstate embedding**: new `app/scripts/embed-tfstate.mjs`
runs as `predev`/`prebuild` hook, pulling live state via `terraform
state pull` (any backend) and writing it to
`app/packages/server/src/generated/tfstate.ts`; `ConfigService` uses it
as a fallback when the runtime `terraform.tfstate` file is absent
- **API error handling**: `api.ts` now throws on non-2xx responses
instead of resolving with the error body, preventing silent failures
downstream; `CostPanel` gets defensive `?.` chaining on `daily`;
`DiscordPanel` catches load errors and shows a user-friendly message
instead of getting stuck on "Loading…"
## Test plan
- [ ] `npm run dev` from `app/` runs `terraform state pull`, embeds real
state, and Nest boots with all DI resolved (no `undefined` service
errors)
- [ ] Games list populates from embedded tfstate when
`terraform/terraform.tfstate` is absent
- [ ] API 500 responses surface as errors in the UI rather than silent
empty state
- [ ] Cost panel renders without crashing when cost data is unavailable
- [ ] Discord panel shows "unavailable" message instead of spinning
forever when infra isn't deployed
- [ ] `npm test` passes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,6 +83,8 @@ When adding a game, only edit `terraform.tfvars`. Don't hand-write new resources
83
83
84
84
`ConfigService.getTfOutputs()` (in `app/packages/server/src/services/ConfigService.ts`) parses `terraform.tfstate` as JSON and caches it in-memory. `invalidateCache()` is called on `/api/games` and `/api/status` to pick up new deploys. The app's container mounts `./terraform:/app/terraform:ro` — this path coupling matters if directory structure changes. The parsed `TfOutputs` shape now also exposes `discord_table_name`, `discord_bot_token_secret_arn`, `discord_public_key_secret_arn`, and `interactions_invoke_url` so `DiscordConfigService` can reach the Discord stores without extra env-var plumbing.
85
85
86
+
**Build-time state embedding**: `app/scripts/embed-tfstate.mjs` runs automatically via `predev` and `prebuild` npm lifecycle hooks. It reads Terraform state (via `terraform state pull`, or a file path from `TF_STATE_PATH` / the first CLI arg) and writes `app/packages/server/src/generated/tfstate.ts` with the state serialized as a JSON literal. `ConfigService` imports this as `EMBEDDED_TFSTATE` and uses it as a fallback when the runtime `terraform.tfstate` file is absent — useful in Docker or CI environments where the Terraform directory isn't mounted. When neither source is available, `getTfOutputs()` returns `null` and callers degrade gracefully. The generated file is committed as `null` (no real state) and is overwritten at dev/build time.
87
+
86
88
### API authentication
87
89
88
90
Every `/api/*` route is gated behind a bearer token via `ApiTokenGuard` in `app/packages/server/src/guards/api-token.guard.ts`, registered globally in `AppModule` as an `APP_GUARD` provider so it applies to every controller automatically. The token comes from env `API_TOKEN` (wins, even when set to empty to deliberately disable) or `api_token` in `server_config.json`. In production (`NODE_ENV=production`), boot aborts in `main.ts` if no token is configured. In dev, a warning is logged and unauthenticated requests are allowed for convenience. The web client stores the token in `localStorage` under key `apiToken` and sends it as `Authorization: Bearer`. Don't remove the guard or bypass it on individual controllers — Copilot flagged the unauthenticated surface as a security issue and this is the fix.
0 commit comments