Skip to content

FullStackHero 10 .NET Starter Kit Release Merge#1152

Draft
iammukeshm wants to merge 289 commits intomainfrom
develop
Draft

FullStackHero 10 .NET Starter Kit Release Merge#1152
iammukeshm wants to merge 289 commits intomainfrom
develop

Conversation

@iammukeshm
Copy link
Copy Markdown
Member

#Architecture

  • Modular monolith with modules for Identity, Multitenancy, Auditing; mediator-based CQRS; background jobs; caching; mailing; storage abstraction.
  • Minimal API host with Identity (JWT, refresh, roles/permissions), Multitenancy (Finbuckle, provisioning lifecycle), Auditing (request/response/security/exception with background sink).
  • Shadcn-inspired MudBlazor wrappers; Dashboard/Profile/Audits pages wired to generated API clients; BFF-style auth delegating handler; theme/layout shell.
  • NSwag config + script to regenerate clients (scripts/openapi/generate-api-clients.ps1 -SpecUrl "<spec>"); Blazor consumes generated clients.
  • Multi-app AWS scaffolding (API/Blazor) with modular structure using Terraform.
  • Mediator Handlers and Validation
  • RateLimiting / Storage / Outbox Pattern

Introduced new projects `FSH.Playground.ServiceDefaults` and
`FSH.Playground.AppHost` to centralize and integrate resilience,
service discovery, and observability features using OpenTelemetry.

- Updated `Directory.Packages.props` to include new packages
  for Aspire services and reorganized existing package versions.
- Added `builder.AddServiceDefaults()` to `Program.cs` to
  integrate service defaults into the application pipeline.
- Created `Extensions.cs` to provide reusable methods for
  configuring OpenTelemetry, resilience, and service discovery.
- Added `appsettings.json` and `appsettings.Development.json`
  for logging and Aspire-specific configurations.
- Updated `FSH.Framework.slnx` to include new projects under
  the `/Playground/` folder.
- Added `launchSettings.json` for local development profiles.
- Updated `Playground.Api.csproj` to reference the new
  `FSH.Playground.ServiceDefaults` project.
Updated `AppHost.cs` to configure PostgreSQL and Redis containers, including database and caching setup.
Modified `Directory.Packages.props` to add `Aspire.Hosting.PostgreSQL` and `Aspire.Hosting.Redis` dependencies, and updated `OpenTelemetry` package versions.
Updated `FSH.Playground.AppHost.csproj` to include necessary package references.
Renamed `CacheOptions` to `CachingOptions` in `appsettings.json` for consistency.
These changes enhance the application's scalability and performance by integrating PostgreSQL as the database provider and Redis for caching.
Enhanced observability with OpenTelemetry for tracing, metrics,
and logging, including support for OTLP exporters and resource
tagging. Introduced `CachingOptions` for Redis configuration
with a default key prefix and fallback to in-memory caching.

Refactored database contexts to include `IHostEnvironment` for
environment-specific configurations, enabling sensitive data
logging and detailed errors in development. Improved multi-tenancy
support by integrating tenant context in various services.

Removed unused projects and files, simplifying the solution
structure. Updated logging levels for better visibility in
production and added caching for refresh tokens to improve
token management.
Replaced OpenTelemetry-based logging with Serilog as the primary
logging library, introducing `HttpRequestContextEnricher` to
enrich log events with HTTP context and user metadata. Enabled
conditional OpenTelemetry integration for metrics and tracing.

Simplified the `OpenTelemetryOptions` class by removing
`LoggingOptions`, `SamplerOptions`, and `JaegerExporterOptions`.
Updated `appsettings.json` to configure Serilog with additional
enrichers (`Environment`, `Process`, `Span`, `Thread`) and the
`OpenTelemetry` sink for external logging.

Removed unused code in `RoleService` and caching logic in
`GenerateTokenCommandHandler` to streamline the codebase. Added
new Serilog dependencies to the project for enhanced logging
capabilities.
Removed `CreateSampler` method from `Extensions.cs` to shift sampling configuration elsewhere. Updated `ConfigureOtlpExporter` to simplify OTLP exporter setup.

Added environment variables in `AppHost.cs` for OTLP exporter configuration, including endpoint, protocol, and enablement.

Standardized OTLP endpoint to `http://localhost:4317` and protocol to `grpc` across `launchSettings.json` and `appsettings.json`. Updated `resourceAttributes` to use `service.name` instead of `app.name` for OpenTelemetry compliance.
Improved exception logging in `GlobalExceptionHandler` by adding detailed context properties and updating log messages. Integrated FluentValidation by registering validators dynamically in `ModuleLoader` and adding dependency injection support via `FluentValidation.DependencyInjectionExtensions`. Simplified `UserImageValidator` instantiation with a default constructor. Updated project files to include necessary FluentValidation dependencies.
Enhanced metrics collection by adding PostgreSQL instrumentation
and dynamic meter configuration in `Extensions`. Introduced a
`MeterNames` property in `MetricsOptions` for custom meter names.

Added `IdentityMetrics` to track token generation metrics in the
Identity module. Updated `TokenService` to log user emails and
increment the `identity_tokens_generated` metric.

Updated `appsettings.json` to include meter names for various
modules. Introduced the `IdentityMetrics` class using the
`System.Diagnostics.Metrics` API for managing Identity module
metrics.
Refactored tenant lifecycle management by introducing a unified `ChangeTenantActivationCommand` and `ChangeTenantActivationEndpoint`, replacing legacy activation/deactivation commands and endpoints.

Added `TenantLifecycleResultDto` and `TenantMigrationStatusDto` for better encapsulation of tenant lifecycle and migration data. Introduced `TenantMigrationsEndpoint` and `TenantMigrationsHealthCheck` to provide detailed migration diagnostics.

Centralized permissions in `MultitenancyConstants` and updated endpoints to use these constants. Enhanced `TenantService` with validation to prevent deactivating the root tenant or leaving no active tenants.

Updated documentation to reflect the new unified activation endpoint and added examples for migration diagnostics. Introduced a new test project (`Multitenancy.Tests.csproj`) and placeholder tests for tenant lifecycle operations.
Updated the solution file to include the new `Multitenacy.Tests` project. Replaced `Multitenancy.Tests.csproj` with a new version, updating the target framework to `net10.0`, adding the `<IsPublishable>` property, and revising dependencies and project references.

Commented out the `TenantLifecycleTests` class, deferring its implementation due to missing configurations for authentication and test tenant environments. These changes restructure the project to improve maintainability and prepare for future enhancements.
Introduced `IdentityPermissionConstants` to centralize user and role
permission constants (`View`, `Create`, `Update`, `Delete`).

Added `DeleteRoleCommandHandler` to handle role deletion using
`IRoleService`. Ensured null checks and asynchronous operation
via `DeleteRoleAsync`.

Included necessary `using` directives to support the new functionality.

Refactor: Introduce Mediator and Centralize Permissions

Refactored the application to use the Mediator pattern, replacing direct service calls with commands and queries. Centralized permission constants in `IdentityPermissionConstants` and updated endpoints to use these constants for consistency.

Enhanced endpoint descriptions, modularity, and maintainability by introducing dedicated namespaces, DTOs, and handlers. Improved validation, error handling, and security. Simplified dependency injection by removing direct service dependencies.

Streamlined role and user management with new commands, queries, and handlers. Added `IsBasic` flag to permissions for better differentiation. Standardized endpoint routing and improved user feedback for operations like password reset and email confirmation.
Upgraded the following NuGet packages:
- `AutoFixture` from `4.18.1` to `5.0.0-preview0012`.
- `Mediator.Abstractions` from `3.1.0-preview.5` to `3.1.0-preview.14`.
- `Mediator.SourceGenerator` from `3.1.0-preview.5` to `3.1.0-preview.14`.

These updates may include new features, bug fixes, or other improvements.
Removed legacy auditing classes, including `Audit`, `AuditBackgroundWorker`, and `AuditHttpMiddleware`, simplifying the architecture. Reintroduced and restructured key auditing components under the `FSH.Framework.Shared.Auditing` namespace, such as `AuditIgnoreAttribute` and `AuditSensitiveAttribute`.

Added new DTOs (`AuditDetailDto`, `AuditSummaryDto`, `AuditSummaryAggregateDto`) and query classes for retrieving audit data (`GetAuditByIdQuery`, `GetAuditsQuery`, etc.). Introduced endpoints for audit retrieval, including by ID, correlation, trace, and summaries.

Updated `AuditingModule` to support API versioning and new endpoints. Refactored `IdentityModule` to rename user and role endpoints for consistency. Enhanced `TenantMigrationsEndpoint` to use Mediator for tenant migration queries.

Added architecture and developer guide documentation to clarify the modular design and development workflow.
Removed metadata, introductory sections, and detailed explanations from `architecture.md`, `developer-guide.md`, `Multitenancy.md`, and `README.md`. This includes architecture overviews, setup guides, module-specific details, and future plans. The changes streamline the documentation by eliminating in-depth technical details and focusing on simplicity.
Added `$RECYCLE.BIN/` to ignore Windows recycle bin files.
Also added `/.bmad` and `/docs` to exclude temporary and
documentation directories, keeping the repository clean.
Added a new Eventing building block to enable reliable and
idempotent integration event handling. Introduced abstractions
(IEventBus, IEventSerializer, IIntegrationEvent, etc.) and
implemented an in-memory event bus with EF Core-based outbox
and inbox stores for persistence.

Integrated the eventing system into the Identity module,
including event publishing for token generation and user
registration. Added recurring Hangfire jobs for outbox
dispatching. Enhanced refresh token handling with validation
and storage.

Added database migrations for InboxMessages and OutboxMessages
tables. Improved logging, exception handling, and Blazor login
with tenant support. Updated solution and project references
to include the Eventing building block.
…nvironments

- Added backend configuration for S3 to manage Terraform state files in both production and staging environments.
- Created main Terraform configuration files for production and staging, defining required providers and modules for application deployment.
- Defined environment-specific variables for production and staging, including VPC CIDR blocks, subnet configurations, S3 bucket names, database credentials, and container configurations.
- Implemented networking module to create VPC, subnets, NAT gateways, and route tables.
- Developed application stack module to provision ECS services, ALB, RDS, and Redis resources.
- Added security groups and IAM roles for ECS services and RDS instances.
- Configured CloudWatch logging for ECS services and defined health checks for load balancers.
- Established S3 bucket module for application data storage with versioning and encryption enabled.
…iles; update Terraform configurations for improved deployment
Added ILogger<Login> to enable detailed logging in the login
process. Enhanced error handling by capturing and logging
HTTP response errors, including status codes and error
messages, for failed login attempts. These changes improve
debugging and observability of authentication issues.
Introduced a GitHub Actions workflow to automate building and pushing
container images for the API and Blazor projects. The workflow triggers
on `push` events to the `develop` branch and includes the following:

- Configured permissions for `contents: read` and `packages: write`.
- Added a `build-and-push` job running on `ubuntu-latest`.
- Steps include:
  - Checkout repository code using `actions/checkout@v4`.
  - Setup .NET SDK version `10.0.x` using `actions/setup-dotnet@v4`.
  - Log in to GitHub Container Registry (GHCR) using `GITHUB_TOKEN`.
  - Build and publish container images for API and Blazor projects.
  - Push the container images to GHCR.

This workflow ensures the latest container images are built and stored
in the registry for the `develop` branch.
Updated the `dotnet publish` commands to use `-p:ContainerImageTags`
instead of `-p:ContainerImageTag`, enabling support for multiple
image tags. Removed explicit `docker push` commands for API and
Blazor images, as the new parameter likely handles this step
automatically or the process has been relocated.
… is set for both API and Blazor image publishing
iammukeshm and others added 30 commits April 3, 2026 16:42
- Proactive refresh detection: verify near-expiry tokens trigger refresh
- Refresh token rotation: verify old tokens rejected after rotation
- Token expiry validation: verify correct expiry window in issued JWTs
- Refreshed token expiry: verify fresh expiry after refresh
…t provisioning

- Extract stale lock cleanup from Hangfire config delegate into
  HangfireStaleLockCleanupService (runs after app starts, not during DI)
- Harden TenantStoreInitializerHostedService and TenantAutoProvisioningHostedService
  for reliability during startup
- Add gitignore entries for Next.js client workspace
…upService

The class is registered via AddHostedService but CA1812 doesn't recognize
DI-based instantiation of internal classes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rator

- DomainEventsInterceptor now catches handler failures in SavedChangesAsync
  so side-effect errors (email, notifications) don't fail already-committed saves
- UserRegisteredEmailHandler catches email failures gracefully — welcome email
  failures must not break user registration
The production Hangfire server uses a 30s SchedulePollingInterval, meaning
enqueued tenant provisioning jobs could wait up to 30s before pickup — longer
than the test's 30s polling timeout. Fix:

- Override AddHangfireServer in test factory with 1s polling interval
- Increase WaitForProvisioningAsync timeout from 30s to 60s
- Fail fast on provisioning failure instead of silently continuing
- Throw TimeoutException instead of silently returning on timeout

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ests

Root cause: all 68 integration tests pass, but the process hangs during
shutdown because:
1. EfCoreInboxStore.MarkProcessedAsync did a blind INSERT, causing
   PK_InboxMessages duplicate key violations when the same event was
   processed via both the direct publish and outbox retry paths concurrently.
   This caused outbox messages to never be marked as processed, leading to
   infinite retry loops.
2. UserRegisteredEmailHandler tried to send real emails in CI (no SMTP
   configured), failing with "Rate limited". Hangfire retried these jobs
   with exponential backoff (10 retries), keeping the process alive past
   the CI timeout.

Fixes:
- Make MarkProcessedAsync idempotent: check HasProcessedAsync before INSERT,
  and catch DbUpdateException for the concurrent race case
- Register a NoOpMailService in the test factory to prevent real SMTP calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ntegration tests

Three issues fixed:

1. OutboxDispatcherHostedService started before migrations, querying
   identity.OutboxMessages that didn't exist yet. Fix: disable via
   EventingOptions:UseHostedServiceDispatcher=false.

2. Production AddHeroJobs() registered a Hangfire server (30s polling) +
   HangfireStaleLockCleanupService. The test factory added a SECOND server
   (1s polling) without removing the first. Fix: remove all Hangfire hosted
   services before registering the test-only InMemory server.

3. HangfireStaleLockCleanupService tried to DELETE FROM hangfire.lock on
   the PostgreSQL Testcontainer which has no Hangfire schema. Fix: removed
   along with other Hangfire hosted services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… and fix provisioning

- Add SyncModel migrations for IdentityDbContext (UserSessions index) and
  AuditDbContext (TenantId column type) — EF Core 10 blocked MigrateAsync
  with "pending model changes" warning, preventing tenant provisioning
- Remove OutboxDispatcherHostedService in test factory — it queries
  OutboxMessages before migrations complete, causing startup errors
- Fix provisioning status endpoint path: /{tenantId}/provisioning (not /status)
- Add login retry for new tenant admin in isolation tests — provisioning
  may still be finalizing when the login attempt fires
- AuditBackgroundWorker: catch OperationCanceledException during shutdown
  instead of logging it at ERROR level — cancellation during host teardown is expected
- Test factory: suppress EF Core and Npgsql log noise during startup provisioning
  race (tenant.Tenants not yet migrated) via Serilog level overrides
…-based cache invalidation, per-version OpenAPI docs, and code quality improvements

- Add Brotli + Gzip response compression middleware
- Add Redis and Hangfire health checks on /health/ready
- Surface traceId and correlationId in ProblemDetails error responses
- Add tag-based cache invalidation (SetItemAsync with tags, RemoveByTagAsync) to ICacheService
- Generate per-version OpenAPI documents (configurable via OpenApiOptions.Versions)
- Add AsNoTracking to read-only EF queries in Group handlers and UpdateGroupCommandHandler
- Replace read-then-delete with ExecuteDeleteAsync in session cleanup
- Add explanatory comments to all intentional broad catch blocks across caching, eventing, jobs, auditing, identity, and multitenancy
- Fix MultitenancyOptionsTests to match actual default values
- Fix string concatenation in structured log calls (IdentityService)
- Add AsNoTracking to read-only queries (GetUserGroupsQueryHandler)
- Add explanatory comments to catch blocks (WebhookDeliveryService, GetTenantMigrationsQueryHandler)
- Add ConfigureAwait to middleware delegate calls (AuditHttpMiddleware)
…dd Quick Start to README

- Add Webhooks.Contracts and Webhooks module to CI pack step (were missing)
- Fix dotnet new template description from .NET 9 to .NET 10
- Add FSH CLI to local tool manifest (.config/dotnet-tools.json)
- Add Quick Start section to README with dotnet new template and git clone paths
- Add NuGet and license badges to README
Remove the Blazor.UI BuildingBlock and FSH.Starter.Blazor Playground app
entirely — the frontend is moving to Next.js. Clean up all references
across solution, CI/CD, Terraform, docs, tests, scripts, and config.

Also fix Redis connectivity under Aspire 13.x which now enables TLS on
the primary Redis port by default. Use the secondary (plain TCP) endpoint
to avoid StackExchange.Redis TLS negotiation issues through the Aspire proxy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o unblock CI

CA1062/CA1861 on auto-generated EF Core migration files and pedantic CA
rules in Integration.Tests were failing the -warnaserror build.

- Migrations.PostgreSQL: NoWarn CA1062, CA1861 (files are regenerated)
- Integration.Tests: NoWarn CA2234, CA2000, CA1062, CA1031, CA1812, CA1056,
  S1481 (rules don't fit test-code pragmatics; CA1812 false-positive on
  DI-instantiated NoOpMailService)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… JavaScript hosting

- Move clients/apps/admin → clients/admin with workspace packages
  (api-client, auth, ui) extracted into clients/admin/packages/
- Bump Aspire hosting packages to 13.2.1, add Aspire.Hosting.JavaScript
- Wire fsh-admin Next.js app into AppHost with npm dev resource,
  API reference, and FSH_API_URL env injection
- Fix .gitignore: exempt clients/**/packages/ from the **/[Pp]ackages/*
  NuGet rule so the Next.js workspace packages are tracked

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the custom ICacheService abstraction and its two hand-rolled
implementations with Microsoft.Extensions.Caching.Hybrid (HybridCache), the
GA'd framework primitive. HybridCache provides built-in L1+L2 layering,
stampede protection, and logical tag-based invalidation — capabilities we
were maintaining ourselves.

Caching building block
- Add Microsoft.Extensions.Caching.Hybrid 10.1.0 package
- Delete ICacheService, CacheServiceExtensions, DistributedCacheService,
  and the misleadingly-named custom HybridCacheService (none of which were
  the real HybridCache)
- Rewrite Extensions.AddHeroCaching() to register HybridCache layered over
  AddStackExchangeRedisCache (Redis) or AddDistributedMemoryCache (fallback)
- Trim CachingOptions: DefaultExpiration (L1+L2), DefaultLocalCacheExpiration
  (L1-only, 2min default to bound cross-node staleness), MaximumKeyLength,
  MaximumPayloadBytes
- Add CacheKeys helper with stable key builders and tag constants

Consumer migration
- UserPermissionService: inject HybridCache, use GetOrCreateAsync with
  [Permissions, User(userId)] tags
- TenantThemeService: inject HybridCache, use GetOrCreateAsync with
  [Themes, Tenant(tenantId)] tags; InvalidateCacheAsync now also calls
  RemoveByTagAsync for tenant-wide purge
- IdempotencyEndpointFilter: probe-only reads via DisableUnderlyingData
  flag; writes use SetAsync with [Idempotency, Tenant(tenantId)] tags

Tests + CI
- New src/Tests/Caching.Tests project (14 tests): key format stability,
  DI registration, end-to-end HybridCache behavior (GetOrCreate, Set,
  Remove, RemoveByTag)
- Add to FSH.Starter.slnx and CI test matrix

Docs
- Rewrite docs/src/content/docs/caching.mdx to document HybridCache usage,
  tag semantics, L1 staleness tradeoff, multitenancy conventions, and
  migration mapping from the old ICacheService
- Update building-blocks-overview.mdx, idempotency.mdx, llms-full.txt to
  reference HybridCache instead of ICacheService

Incidental
- Fix indentation drift on Directory.Packages.props coverlet.collector entry

Build green with -warnaserror; all 477 tests pass (220 Identity + 93
Multitenancy + 60 Auditing + 47 Architecture + 43 Generic + 14 Caching).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eless factories, OTel decorator

Squeezes the HybridCache integration for production performance. None of the cached
call sites change behavior; the optimizations are all under the surface.

L1 reuse via [ImmutableObject(true)]
- Cached DTOs (TenantThemeDto + nested palette/branding/typography/layout, plus
  CachedIdempotentResponse) are now sealed records with [ImmutableObject(true)],
  unlocking HybridCache's pointer-copy L1 reuse instead of re-deserializing JSON
  on every L1 hit (~5–10 µs and ~1 KB allocation per hit avoided).
- New PermissionSet wrapper (sealed + ImmutableObject + ImmutableArray<string>)
  replaces List<string> as the cached value in UserPermissionService. The public
  contract still returns List<string> (one tiny copy on the way out preserves
  binary compat with IUserService consumers).

Stateless factory overloads (zero closure allocation)
- UserPermissionService and TenantThemeService now use HybridCache's
  GetOrCreateAsync<TState, T> overload with static method group factories. No
  closure capture, no per-call delegate allocation, even on L1 hits.
- Static-readonly HybridCacheEntryOptions and tag arrays hoisted to avoid
  per-call allocations.

Permission factory micro-optimization
- Replaced the old N+1 RoleClaims loop with a single IN query across all role
  IDs — cheaper on the cache-miss path that consumers actually hit.

OTel observability via ObservableHybridCache decorator
- New decorator wraps the HybridCache registration transparently. Records
  fsh.cache.{hits,misses,invalidations} counters and fsh.cache.factory.duration
  histogram on the FSH.Caching meter, plus per-op Activity spans tagged with
  cache.system=fsh.hybrid.
- Wired into the OTel pipeline alongside FSH.Hangfire (Web/Observability).
- Hit/miss tracked via a struct-state wrapper that flows through the inner
  TState parameter — avoids allocating a closure for the wrapper itself.

Idempotency probe pattern fixed
- Replaced the GetOrCreateAsync + DisableUnderlyingData hack (a known
  anti-pattern, dotnet/aspnetcore#57191) with a real IDistributedCache.GetAsync
  probe. Writes still go through HybridCache.SetAsync so tag invalidation works.

Compression tuning
- UserPermissionService entries opt out of compression
  (HybridCacheEntryFlags.DisableCompression) — payload is < 4 KB so compression
  CPU exceeds the network savings.

Tests (+19 passing, 477 → 496)
- CachedTypeContractTests: locks in the sealed + [ImmutableObject(true)] contract
  for every cached DTO. Adding a cached type without these guarantees fails CI.
  PermissionSet is loaded via reflection (internal type, no InternalsVisibleTo).
- ObservableHybridCacheTests: MeterListener-backed assertions that hits, misses,
  invalidations, and factory duration measurements actually fire. Serialized
  via [Collection] to avoid cross-test interference on the global Meter.
- Integration.Tests/HybridCacheRedisTests (Testcontainers Redis):
  * Round-trip serialization through real Redis
  * SetAsync persists bytes to L2
  * RemoveByTagAsync invalidates within an instance
  * StackExchange.Redis is verified to implement IBufferDistributedCache so
    HybridCache gets zero-copy reads (locked in against regression)

Docs
- Added "Performance characteristics" section to caching.mdx documenting all
  six optimizations.

Incidentals
- Fix Directory.Packages.props indentation drift (left over from a previous PR)
- Add Testcontainers.Redis 4.5.0 package version

Build green with -warnaserror; 496 tests passing (33 Caching + 220 Identity +
93 Multitenancy + 60 Auditing + 47 Architecture + 43 Generic). Redis
integration tests verified locally with Docker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete clients/admin/ monorepo (Next.js + pnpm workspace)
- Unwire admin from Aspire AppHost; drop Aspire.Hosting.JavaScript
- Add requirements/frontend-and-platform.md with locked decisions
  for the React + Vite rewrite (admin + dashboard), rate limiting,
  impersonation, SignalR, Recharts, internal billing, and quota metering

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ously with RFC 9457 rejection

- Replace OR-cascade partition key with PartitionedRateLimiter.CreateChained
  over three independent tenant/user/IP buckets; any exceeded bucket rejects
- Split RateLimitingOptions.Global into Tenant/User/Ip (defaults 1000/200/300 req/min);
  Auth policy unchanged (10 req/min for login throttling)
- Add OnRejected handler emitting ProblemDetails + Retry-After + traceId
  to match GlobalExceptionHandler behavior
- Remove unused "global" named policy; keep "auth"
- Update dev/prod appsettings and docs (rate-limiting, web-building-block,
  configuration-reference)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e + docker-compose

- Add ServiceUrl, AccessKey, SecretKey, ForcePathStyle to S3StorageOptions
  (all optional; defaults preserve AWS S3 behavior)
- Honor the new fields in Storage/Extensions when building AmazonS3Client:
  custom endpoint + path-style addressing + explicit BasicAWSCredentials;
  fall back to ambient credential chain when keys absent; default
  AuthenticationRegion to us-east-1 when ServiceUrl is set without a Region
- docker-compose: add minio + minio-init one-shot (creates fsh-uploads bucket,
  enables anonymous download); api waits on init completion before starting
- Aspire AppHost: add MinIO (persistent volume, console on 9001) plus
  minio-init (runs mc to bootstrap the bucket, WaitFor(minio));
  fsh-api uses WaitForCompletion(minioInit) and gets Storage__S3__* env vars
  from endpoint/parameter references
- Docs: file-storage.mdx documents new options, adds a MinIO config snippet,
  notes the bundled init container

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds start/end impersonation endpoints under api/v1/identity/impersonation.
Platform operators (root tenant) may impersonate any tenant's user; tenant
admins are limited to users within their own tenant. Impersonation tokens
are access-only (no refresh) and carry act_sub / act_tenant claims per
RFC 8693 so downstream services know who is acting. End-impersonation
reads those claims to re-issue a normal token for the original actor.

Every start/end emits a SecurityAudit entry so tenant admins already see
a scoped audit trail via the existing GetSecurityAudits query.

Cross-tenant claim resolution bypasses Finbuckle's tenant filter via
IgnoreQueryFilters(); V1 resolves direct role claims only (group roles
from the target tenant are not yet included).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tion

Introduces a Quota building block that tracks per-tenant monthly usage
for counter-based resources (ApiCalls today; StorageBytes/Users/
ActiveFeatureFlags reserved for gauge providers). Redis-backed counters
use atomic INCRBY with a TTL aligned to the next UTC month; an in-memory
fallback covers dev/test. Rejections return HTTP 429 + RFC 9457
ProblemDetails with a Retry-After header and flag HttpContext.Items so
AuditHttpMiddleware can tag the activity with AuditTag.OutOfQuota — no
reverse dependency from the building block into the audit module.

AppTenantInfo now carries a Plan name and a QuotaLimits override map
(jsonb + JSON value converter); a plan resolver consults tenant
overrides, then the plan catalog, then QuotaOptions.DefaultPlan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Registers UserCountQuotaGaugeProvider for tenant user-count reads via
UserManager with tenant-filter bypass. IQuotaService lifetime moved to
scoped so gauge providers with scoped deps (UserManager, DbContext)
resolve per request; InMemoryQuotaStore singleton preserves counter
state across scopes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wraps IStorageService with a decorator that charges StorageBytes on
upload and refunds on delete. Rejects over-quota uploads with HTTP
507 via CustomException; rolls the counter back if the inner PUT
throws so a failed write can't consume quota.

Quota counters split into periodic (ApiCalls, monthly window) and
perpetual (StorageBytes, no reset). StorageBytes keys drop the
period suffix and skip TTL assignment in Redis.

Adds IStorageService.GetSizeAsync so the decorator can debit the
exact byte count on delete (Local uses FileInfo.Length, S3 uses
GetObjectMetadata.ContentLength). Architecture test updated to
allow Storage→Quota since quota metering is a first-class
cross-cutting concern for storage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Domain: BillingPlan, Subscription, Invoice/InvoiceLineItem, UsageSnapshot with Guid.CreateVersion7 IDs
- BillingDbContext (schema `billing`) + InitialBilling migration; active-subscription uniqueness enforced via filtered index
- UsageReporter captures {Used, Limit} per QuotaResource at period close for invoice reproducibility
- BillingService.GenerateInvoiceForPeriodAsync issues draft invoices with base fee + overage lines
- MonthlyInvoiceJob (Hangfire recurring, 5 0 1 * * UTC) bills every active tenant for the previous month
- Plan/subscription/invoice/usage endpoints under api/v1/billing; permissions: Permissions.Billing.View/Manage
- Pure usage reporting + invoicing; no payment-processor integration

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SseTokenService issues short-lived opaque tokens (IDistributedCache, 30s TTL, single-use) since EventSource cannot send Authorization headers
- POST /api/v1/sse/token (JWT-authenticated) → { token }; GET /api/v1/sse/stream?token=<guid> streams events
- 15s heartbeat (`:heartbeat\n\n`) keeps idle connections alive through proxies
- SseConnectionManager keyed per-connection so multi-tab users keep every stream open
- Wired into AddHeroPlatform (EnableSse) and UseHeroPlatform (MapSseEndpoints)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Standalone Vite app at clients/admin with auth, tenant list, and app
shell. No pnpm workspace so Aspire can mount it as a plain ExecutableResource.

- React 19 + TypeScript + Tailwind 4 CSS-first + shadcn/ui (new-york)
- TanStack Query v5, react-router v7
- JWT auth with localStorage + single-flight refresh on 401
- First real page: paginated tenants list via GET /api/v1/tenants/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds Aspire.Hosting.JavaScript and registers fsh-admin as an npm 'dev'
resource. VITE_API_BASE_URL is plumbed from the fsh-api endpoint so the
Vite dev proxy forwards /api calls through Aspire's service discovery.

dotnet run --project src/Playground/FSH.Starter.AppHost now brings up
postgres + redis + minio + api + admin together.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ge charts

Second standalone Vite app at clients/dashboard, wired into Aspire on
port 5174. Separate localStorage namespace (fsh.dashboard.*) so admin
and dashboard can be logged in side-by-side.

SSE consumer uses fetch streaming rather than the native EventSource API.
EventSource auto-reconnects with the original URL, which breaks our
single-use opaque-token model (each reconnect would replay an already-
consumed token and get 401'd). Fetch streaming lets us mint a fresh
token per (re)connect, apply the tenant header to the stream, and run
an explicit exponential backoff (1s → 30s).

Overview page renders current-period usage vs. plan limits as a
Recharts bar chart (overage bars turn red), subscription status, and
the live event feed. Separate /activity page hosts a larger feed view,
/invoices shows billing history.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants