Wires the budget container to the OTel Collector via the shared external
telemetry network. Endpoint, protocol, and service name come from env vars
so the collector address is not baked into application code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds OTel packages (Extensions.Hosting, Instrumentation.AspNetCore/Http/EFCore,
Exporter.OTLP) and configures logging + tracing in Program.cs. Endpoint is
intentionally left unset in code — read from OTEL_EXPORTER_OTLP_ENDPOINT at runtime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration adds IsDeleted/DeletedAt to Budgets, Incomes, Outgos, BudgetShares
- xmin concurrency token configured as shadow property (uint, xid type) — not added
as a column in the migration since xmin is a PostgreSQL system column
- Budget.RowVersion property removed; concurrency tracked via shadow property only
- Add ISoftDeletable interface with IsDeleted/DeletedAt
- Implement on Budget, Income, Outgo, BudgetShare; add RowVersion (xmin) to Budget
- Configure EF global query filters and xmin concurrency token
- Replace Remove() with soft delete in all delete endpoints
- Wrap Budget.Update SaveChanges in DbUpdateConcurrencyException catch
- Install @tanstack/react-query
- Create queryClient with MutationCache for global toast on success/error
- QueryToastBridge component bridges module-level handlers to ToastProvider
- Wrap App in QueryClientProvider
- Create domain hook files: budgets, incomes, outgos, shares, summary
- Update all 5 pages to use hooks; remove inline useEffect/useState fetch logic
- DnD reorder pages use local displayItems state synced from query data
Deletes the hand-rolled AuthContext/UserManager setup and replaces it
with AuthProvider from react-oidc-context. onSigninCallback clears the
OIDC code params from the URL (unless an error is present). TokenSync
bridges the library token into the existing api/client setTokenProvider
pattern. AuthGuard updated to use auth.isLoading/isAuthenticated/
signinRedirect from the library. CallbackPage simplified to a passive
error renderer — react-oidc-context processes the OIDC exchange itself.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Budget.Core: entities, DTOs, enums, FrequencyCalculator (no EF/ASP.NET deps)
Budget.Infrastructure: AppDbContext, migrations, BudgetAuthorizationService
Budget.Api: controllers, middleware, Program.cs — references both projects
EF and Npgsql packages moved to Infrastructure; Api retains only JwtBearer,
HealthChecks, and EF.Design (needed for dotnet ef CLI). Dockerfile updated
to copy all three project directories before publishing. Migration namespaces
updated from Budget.Api.Data.* to Budget.Infrastructure.Data.* and model
type strings updated to Budget.Core.Models.* in the snapshot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Joins web (nginx-proxy), apps-internal (database), and auth-public
(OIDC discovery without hairpinning) networks. External networks are
pre-created by the stwaddle infra compose; apps-db is defined here
so the budget service can be deployed standalone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renames VITE_AUTH_* to VITE_OIDC_* to match the stack convention.
Adds a dedicated VITE_OIDC_POST_LOGOUT_REDIRECT_URI instead of deriving
it from the redirect URI via string replace. Switches from Dockerfile
ARG/ENV to a committed src/Budget.Client/.env so Vite picks up
production values at build time without needing build-arg overrides.
.env.local is gitignored for localhost dev overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Catches all unhandled exceptions, logs them, and returns
{ "error": "An unexpected error occurred." } with HTTP 500.
Registered before all other middleware so nothing leaks through.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both policies partition by sub claim with IP fallback. Global limiter
applies to all requests; writes policy is applied via
[EnableRateLimiting("writes")] on every POST, PUT, and DELETE action
across all five controllers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clears KnownNetworks/KnownProxies to trust X-Forwarded-For from any
upstream, since nginx-proxy sits at a dynamically assigned internal IP.
Without this, RemoteIpAddress is always the proxy IP, breaking any
per-client IP resolution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocks startup for up to 2 minutes retrying the OIDC discovery doc fetch,
then proceeds anyway. Prevents JWT middleware from failing to initialize
when the auth and app containers start simultaneously.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Authority, Audience, and MetadataAddress are not secrets so they belong
in committed config rather than runtime env vars. MetadataAddress points
to the internal Docker URL for JWKS fetch, avoiding nginx hairpinning;
it is blanked in Development so the JWT middleware falls back to
Authority-based discovery. RequireHttpsMetadata is disabled only when
MetadataAddress is set (internal http URL).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove OwnerUserId from BudgetDto: OIDC sub of the budget owner was
being returned to all collaborators (including View-only users)
- Remove SharedWithUserId from ShareDto: other users' internal OIDC subs
were visible to anyone with read access to a budget
- Delete MeController: scaffolding endpoint that returned sub to the
browser; no legitimate frontend use case
- Restrict /healthz to require authorization: prevents unauthenticated
probing of database connectivity
- Add input validation annotations to all request DTOs: [Required],
[MaxLength], [Range(0,0.9999)] on EffectiveTaxRate, [EmailAddress] on
share email — [ApiController] now returns 400 instead of 500 for
invalid input hitting DB constraints
- Replace User.FindFirst("sub")!.Value with GetUserId() extension across
all controllers: returns 401 instead of NullReferenceException (500)
if a token lacks a sub claim
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README covers all env vars, docker-compose/env examples, and full
auth server setup (scope, client registration, user roles).
Dockerfile now accepts VITE_AUTH_* build args with production defaults
so the values are baked into the client bundle correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ErrorBoundary component wrapping the whole app
- Add ToastProvider with showError/showInfo; Income and Outgo pages use it for API errors
- Add LoadingSkeleton component with shimmer animation; Income and Outgo show it while loading
- Add confirm-on-delete dialogs for income and outgo rows
- Apply EF migrations automatically on startup via MigrateAsync()
- Add /healthz health check endpoint using DbContext check
- Add Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore package
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add SummaryController: computes monthly income, breakdown by type (Need/Want/Save/Unspent), and pre-tax income
- Need/Want/Save get target% (50/30/20), maxAmount, and remaining; Unspent shows totals only
- PUT /summary/tax-rate updates EffectiveTaxRate on the budget (no new migration needed)
- Add SummaryDto, SummaryBreakdownItem, PreTaxIncomeDto DTOs
- Add Summary page: income header cards, type breakdown table with ⓘ tooltip for target%,
pre-tax section with editable tax rate field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add JWT Bearer auth to ASP.NET (authority/audience from AUTH__AUTHORITY / AUTH__AUDIENCE config)
- Add KnownUserMiddleware: upserts KnownUser and resolves pending shares on each authenticated request
- Add MeController as a guarded test endpoint (/api/me)
- Add oidc-client-ts + react-router-dom to client
- Create AuthContext/AuthProvider with login, logout, token storage
- Create AuthGuard component protecting all budget routes
- Add stub /callback page for OIDC redirect handling
- Wire up all routes in App.tsx with SPA routing structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>