FullStackHero 10 .NET Starter Kit Release Merge#1152
Draft
iammukeshm wants to merge 289 commits intomainfrom
Draft
FullStackHero 10 .NET Starter Kit Release Merge#1152iammukeshm wants to merge 289 commits intomainfrom
iammukeshm wants to merge 289 commits intomainfrom
Conversation
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.
…r login and push commands
… is set for both API and Blazor image publishing
… Blazor containers
…r API and Blazor images
…API and Blazor publishing
- 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#Architecture
scripts/openapi/generate-api-clients.ps1 -SpecUrl "<spec>"); Blazor consumes generated clients.