Updated CLAUDE.md and deployment script

This commit is contained in:
Spencer Twaddle
2026-05-05 07:38:31 -05:00
parent efde0f952b
commit 69ec754775
5 changed files with 15 additions and 55 deletions
+9 -2
View File
@@ -30,7 +30,7 @@ npm run lint # eslint
### Docker ### Docker
```powershell ```powershell
.\build-budget-image.ps1 # builds multi-stage image and pushes to docker.stwaddle.com/budget:latest .\deploy.ps1 # builds image, pushes to docker.stwaddle.com/budget:latest, and deploys to prod via SSH
``` ```
## Architecture ## Architecture
@@ -71,11 +71,18 @@ src/
- **Inline table editing**: rows show read-only data; clicking a cell enters edit mode in-place using a `useState(editing)` toggle. New rows use a separate input row appended to `<tbody>` (not a modal). - **Inline table editing**: rows show read-only data; clicking a cell enters edit mode in-place using a `useState(editing)` toggle. New rows use a separate input row appended to `<tbody>` (not a modal).
- **Sorting**: `SortableHeader` component and `SortState` type in `src/components/SortableHeader.tsx`. Sorting is client-side only and derives a view from the drag-ordered `displayItems` array — clearing sort restores drag order. Drag handles are disabled while a sort is active. - **Sorting**: `SortableHeader` component and `SortState` type in `src/components/SortableHeader.tsx`. Sorting is client-side only and derives a view from the drag-ordered `displayItems` array — clearing sort restores drag order. Drag handles are disabled while a sort is active.
- **Frequency calculations** for real-time previews are in `src/utils/frequency.ts`, mirroring the C# `FrequencyCalculator` exactly. - **Frequency calculations** for real-time previews are in `src/utils/frequency.ts`, mirroring the C# `FrequencyCalculator` exactly.
- **Drag-and-drop ordering**: `@dnd-kit` is used on income/outgo rows. Row order is persisted to the server. Sort handles are hidden while a column sort is active.
- **Auth**: `react-oidc-context` wraps the whole app; `AuthGuard` protects all budget routes; `TokenSync` wires the OIDC access token into the API client on every auth state change. After OIDC callback, navigation uses `window.location.replace('/')` (not `history.replaceState`) so React Router picks up the URL change. - **Auth**: `react-oidc-context` wraps the whole app; `AuthGuard` protects all budget routes; `TokenSync` wires the OIDC access token into the API client on every auth state change. After OIDC callback, navigation uses `window.location.replace('/')` (not `history.replaceState`) so React Router picks up the URL change.
### Local development environment
Required env vars when running the API locally (set in `appsettings.Development.json` or shell):
- `ConnectionStrings__DefaultConnection` — Postgres connection string
- `Oidc__Audience``budget_api`
- `OTEL_EXPORTER_OTLP_ENDPOINT` — optional; omit or set to skip telemetry export
### Auth / OIDC ### Auth / OIDC
- Authority: `https://auth.stwaddle.com/` (OpenIddict-based, at `src.Auth.Server` in a sibling repo) - Authority: `https://auth.stwaddle.com/` (OpenIddict-based, at `src.Auth.Server` in a sibling repo)
- API audience claim: `budget-api` (must match `AUTH__AUDIENCE` env var) - API audience claim: `budget_api` (underscore — must match `Oidc__Audience` env var)
- Scope: `budget_api` (underscore, not hyphen) - Scope: `budget_api` (underscore, not hyphen)
- Roles `admin` and `user` are granted per-user on the client registration in the auth server admin panel. A user with no role gets `access_denied` at the authorize endpoint. - Roles `admin` and `user` are granted per-user on the client registration in the auth server admin panel. A user with no role gets `access_denied` at the authorize endpoint.
- Frontend OIDC config is baked into the build via `VITE_*` env vars; override locally in `src/Budget.Client/.env.local` - Frontend OIDC config is baked into the build via `VITE_*` env vars; override locally in `src/Budget.Client/.env.local`
-4
View File
@@ -1,4 +0,0 @@
$image = "docker.stwaddle.com/budget:latest"
docker build -t $image .
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
docker push $image
+6 -2
View File
@@ -1,2 +1,6 @@
.\build-image.ps1 $image = "docker.stwaddle.com/budget:latest"
.\update-container.ps1 docker build -t $image .
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
docker push $image
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
ssh stwaddle_com "cd stwaddlecom; docker compose pull budget; docker compose down budget; docker compose up -d budget"
-46
View File
@@ -1,46 +0,0 @@
services:
budget:
image: ${IMAGE_REGISTRY:-}budget:latest
environment:
- VIRTUAL_HOST=${BUDGET_VIRTUAL_HOST:-budget.stwaddle.com}
- LETSENCRYPT_HOST=${BUDGET_LETSENCRYPT_HOST:-budget.stwaddle.com}
- VIRTUAL_PORT=8080
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Production}
- ConnectionStrings__DefaultConnection=Host=apps-db;Port=5432;Database=${POSTGRES_DB:-budget};Username=${POSTGRES_USER:-budget};Password=${POSTGRES_PASSWORD}
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT}
- OTEL_EXPORTER_OTLP_PROTOCOL=${OTEL_EXPORTER_OTLP_PROTOCOL}
- OTEL_SERVICE_NAME=budget
depends_on:
- apps-db
- auth
networks:
- web
- apps-internal
- auth-public
- telemetry
restart: unless-stopped
apps-db:
image: postgres:16-alpine
environment:
- POSTGRES_DB=${POSTGRES_DB:-budget}
- POSTGRES_USER=${POSTGRES_USER:-budget}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- apps-db-data:/var/lib/postgresql/data
networks:
- apps-internal
restart: unless-stopped
networks:
web:
external: true
apps-internal:
external: true
auth-public:
external: true
telemetry:
external: true
volumes:
apps-db-data:
-1
View File
@@ -1 +0,0 @@
ssh stwaddle_com "cd stwaddlecom; docker compose pull budget; docker compose down budget; docker compose up -d budget"