Updated plan to fix issues
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Commands
|
||||
|
||||
### Backend (ASP.NET Core)
|
||||
```bash
|
||||
# Run API (must listen on port 5000 for the Vite proxy to work)
|
||||
cd src/Budget.Api
|
||||
ASPNETCORE_URLS=http://localhost:5000 dotnet run
|
||||
|
||||
# Build
|
||||
dotnet build src/Budget.Api/Budget.Api.csproj
|
||||
|
||||
# Add an EF Core migration
|
||||
dotnet ef migrations add <Name> --project src/Budget.Infrastructure --startup-project src/Budget.Api
|
||||
|
||||
# Remove last migration (before it has been applied)
|
||||
dotnet ef migrations remove --project src/Budget.Infrastructure --startup-project src/Budget.Api
|
||||
```
|
||||
|
||||
### Frontend (Vite/React)
|
||||
```bash
|
||||
cd src/Budget.Client
|
||||
npm run dev # dev server at http://localhost:5173
|
||||
npm run build # tsc + vite build (what CI and Docker use)
|
||||
npm run lint # eslint
|
||||
```
|
||||
|
||||
### Docker
|
||||
```powershell
|
||||
.\build-budget-image.ps1 # builds multi-stage image and pushes to docker.stwaddle.com/budget:latest
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The app is a single Docker container: the .NET API serves the compiled React SPA as static files and also handles all `/api` requests. In development the two processes run separately; Vite proxies `/api` to `http://localhost:5000`.
|
||||
|
||||
### Project layout
|
||||
```
|
||||
src/
|
||||
Budget.Core/ # Domain models, DTOs, FrequencyCalculator — no EF, no ASP.NET
|
||||
Budget.Infrastructure/# EF Core DbContext, migrations, BudgetAuthorizationService
|
||||
Budget.Api/ # ASP.NET Core controllers, middleware, Program.cs
|
||||
Budget.Client/ # Vite/React SPA
|
||||
```
|
||||
|
||||
### Data model
|
||||
- **Budget** (owner, name) → owns **Incomes**, **Outgos**, **BudgetShares**
|
||||
- All entities use **soft delete** (`IsDeleted` / `DeletedAt`) with a global EF query filter — hard deletes are not used
|
||||
- **Budget** uses PostgreSQL's `xmin` shadow property as an EF concurrency token
|
||||
- **BudgetShare** links a budget to another user by email; `IsPending = true` until the target user logs in (resolved by `KnownUserMiddleware`)
|
||||
- **Frequency** is a C# enum (`Monthly`, `Biweekly`, `Annually`, etc.); `FrequencyCalculator` converts any frequency to monthly/annual equivalents
|
||||
- The 50/30/20 rule is hardcoded in `SummaryController`: Need=50%, Want=30%, Save=20%
|
||||
|
||||
### API conventions
|
||||
- All controllers require `[Authorize(Roles = "admin,user")]` — tokens that lack a `role` claim matching either value get a 403
|
||||
- `RoleClaimType = "role"` and `NameClaimType = "sub"` are set in JWT options; role checks work with `User.IsInRole()`
|
||||
- `BudgetAuthorizationService` centralises read/write/owner checks against the budget's owner and any accepted shares — always call `CanReadAsync` / `CanWriteAsync` / `IsOwnerAsync` before touching budget data
|
||||
- The `TryGetUserId` helper pattern (returns an `IActionResult?` error or out-params the user id) is used consistently across all controllers
|
||||
- **Enum serialisation**: `JsonStringEnumConverter` is registered globally, so all C# enums are sent/received as strings (e.g. `"Monthly"`, not `5`)
|
||||
- Write endpoints use the `"writes"` rate-limit policy (30 req/min per user); reads use the global limiter (120 req/min)
|
||||
- EF migrations run automatically via `MigrateAsync()` at startup
|
||||
|
||||
### Frontend conventions
|
||||
- **All server state** goes through TanStack Query (`src/api/`). Query keys follow the pattern `['budgets']`, `['budgets', id]`, `['budgets', id, 'summary']`, etc.
|
||||
- **All forms** use `react-hook-form` + `zod` schemas defined in `src/schemas/index.ts`. Never use uncontrolled inputs outside of RHF.
|
||||
- **CSS design system** lives entirely in `src/index.css` as CSS custom properties — no Tailwind, no CSS modules. Use the existing variables (`--color-primary`, `--green-*`, etc.) rather than hardcoded hex values. `App.css` is intentionally near-empty.
|
||||
- **Icons** come from `lucide-react` (tree-shaken per-import). Use `btn-icon` class for icon-only buttons; always add a `title` attribute for accessibility.
|
||||
- **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.
|
||||
- **Frequency calculations** for real-time previews are in `src/utils/frequency.ts`, mirroring the C# `FrequencyCalculator` exactly.
|
||||
- **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 / OIDC
|
||||
- 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)
|
||||
- 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.
|
||||
- Frontend OIDC config is baked into the build via `VITE_*` env vars; override locally in `src/Budget.Client/.env.local`
|
||||
Reference in New Issue
Block a user