├── src
├── BuildingBlocks
│ ├── Blazor.UI
│ │ ├── wwwroot
│ │ │ └── fsh.css
│ │ ├── GlobalUsings.cs
│ │ ├── Components
│ │ │ ├── Title
│ │ │ │ └── FshPageTitle.razor
│ │ │ ├── FshScripts.razor
│ │ │ ├── Layouts
│ │ │ │ ├── FshBaseLayout.razor.cs
│ │ │ │ └── FshDrawerHeader.razor
│ │ │ ├── FshHead.razor
│ │ │ ├── Navigation
│ │ │ │ └── FshBreadcrumbs.razor
│ │ │ ├── Base
│ │ │ │ └── FshComponentBase.cs
│ │ │ ├── Tabs
│ │ │ │ └── FshTabs.razor
│ │ │ ├── Cards
│ │ │ │ └── FshCard.razor
│ │ │ ├── Display
│ │ │ │ └── FshBadge.razor
│ │ │ ├── Feedback
│ │ │ │ ├── FshAlert.razor
│ │ │ │ └── Snackbar
│ │ │ │ │ └── FshSnackbar.razor.cs
│ │ │ ├── Inputs
│ │ │ │ ├── FshSwitch.razor
│ │ │ │ └── FshCheckbox.razor
│ │ │ ├── Avatars
│ │ │ │ └── FshAvatar.razor
│ │ │ ├── Button
│ │ │ │ └── FshButton.razor
│ │ │ ├── Chips
│ │ │ │ └── FshChip.razor
│ │ │ └── Data
│ │ │ │ ├── Pagination
│ │ │ │ └── FshPagination.razor
│ │ │ │ └── FshTable.razor
│ │ ├── _Imports.razor
│ │ ├── Blazor.UI.csproj
│ │ └── ServiceCollectionExtensions.cs
│ ├── Web
│ │ ├── IFshWeb.cs
│ │ ├── Origin
│ │ │ └── OriginOptions.cs
│ │ ├── Modules
│ │ │ ├── IModuleConstants.cs
│ │ │ ├── IModule.cs
│ │ │ └── FshModuleAttribute.cs
│ │ ├── RateLimiting
│ │ │ ├── FixedWindowPolicyOptions.cs
│ │ │ └── RateLimitingOptions.cs
│ │ ├── Cors
│ │ │ └── CorsOptions.cs
│ │ ├── Security
│ │ │ ├── SecurityExtensions.cs
│ │ │ └── SecurityHeadersOptions.cs
│ │ ├── Observability
│ │ │ └── Logging
│ │ │ │ └── Serilog
│ │ │ │ └── StaticLogger.cs
│ │ ├── Mediator
│ │ │ └── Extensions.cs
│ │ ├── OpenApi
│ │ │ └── OpenApiOptions.cs
│ │ └── Versioning
│ │ │ └── Extensions.cs
│ ├── Core
│ │ ├── IFshCore.cs
│ │ ├── Domain
│ │ │ ├── IEntity.cs
│ │ │ ├── IHasTenant.cs
│ │ │ ├── AggregateRoot.cs
│ │ │ ├── IHasDomainEvents.cs
│ │ │ ├── ISoftDeletable.cs
│ │ │ ├── IDomainEvent.cs
│ │ │ ├── IAuditableEntity.cs
│ │ │ ├── DomainEvent.cs
│ │ │ └── BaseEntity.cs
│ │ ├── Common
│ │ │ └── QueryStringKeys.cs
│ │ ├── Context
│ │ │ ├── ICurrentUserInitializer.cs
│ │ │ └── ICurrentUser.cs
│ │ ├── Abstractions
│ │ │ └── IAppUser.cs
│ │ ├── Core.csproj
│ │ └── Exceptions
│ │ │ ├── NotFoundException.cs
│ │ │ ├── ForbiddenException.cs
│ │ │ └── UnauthorizedException.cs
│ ├── Shared
│ │ ├── Multitenancy
│ │ │ ├── IAppTenantInfo.cs
│ │ │ └── MultitenancyConstants.cs
│ │ ├── Identity
│ │ │ ├── AuditingPermissionConstants.cs
│ │ │ ├── CustomClaims.cs
│ │ │ ├── ClaimConstants.cs
│ │ │ ├── ResourceConstants.cs
│ │ │ ├── Authorization
│ │ │ │ └── EndpointExtensions.cs
│ │ │ ├── ActionConstants.cs
│ │ │ └── RoleConstants.cs
│ │ ├── Persistence
│ │ │ ├── DbProviders.cs
│ │ │ ├── PagedResponse.cs
│ │ │ └── IPagedQuery.cs
│ │ ├── Shared.csproj
│ │ └── Auditing
│ │ │ └── AuditAttributes.cs
│ ├── Mailing
│ │ ├── Services
│ │ │ └── IMailService.cs
│ │ ├── MailOptions.cs
│ │ ├── Mailing.csproj
│ │ └── Extensions.cs
│ ├── Persistence
│ │ ├── IConnectionStringValidator.cs
│ │ ├── IDbInitializer.cs
│ │ ├── Specifications
│ │ │ ├── OrderExpression.cs
│ │ │ ├── ISpecificationOfTResult.cs
│ │ │ └── SpecificationOfTResult.cs
│ │ └── Persistence.csproj
│ ├── Jobs
│ │ ├── HangfireOptions.cs
│ │ └── Jobs.csproj
│ ├── Storage
│ │ ├── DTOs
│ │ │ └── FileUploadRequest.cs
│ │ ├── S3
│ │ │ └── S3StorageOptions.cs
│ │ ├── Services
│ │ │ └── IStorageService.cs
│ │ ├── Storage.csproj
│ │ └── FileType.cs
│ ├── Eventing
│ │ ├── Abstractions
│ │ │ ├── IEventSerializer.cs
│ │ │ ├── IIntegrationEventHandler.cs
│ │ │ ├── IEventBus.cs
│ │ │ └── IIntegrationEvent.cs
│ │ ├── Inbox
│ │ │ └── IInboxStore.cs
│ │ ├── Outbox
│ │ │ └── IOutboxStore.cs
│ │ ├── EventingOptions.cs
│ │ └── Eventing.csproj
│ └── Caching
│ │ ├── ICacheService.cs
│ │ ├── CachingOptions.cs
│ │ └── Caching.csproj
├── Playground
│ ├── FSH.Playground.AppHost
│ │ ├── .aspire
│ │ │ └── settings.json
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ └── FSH.Playground.AppHost.csproj
│ ├── Playground.Blazor
│ │ ├── wwwroot
│ │ │ └── favicon.ico
│ │ ├── Services
│ │ │ ├── Api
│ │ │ │ └── ApiClients.cs
│ │ │ └── CookieAuthenticationStateProvider.cs
│ │ ├── appsettings.Development.json
│ │ ├── Components
│ │ │ ├── Layout
│ │ │ │ └── EmptyLayout.razor
│ │ │ ├── Pages
│ │ │ │ ├── Home.razor
│ │ │ │ └── Counter.razor
│ │ │ ├── Routes.razor
│ │ │ ├── _Imports.razor
│ │ │ └── App.razor
│ │ ├── appsettings.json
│ │ ├── appsettings.Production.json
│ │ └── Properties
│ │ │ └── launchSettings.json
│ ├── Playground.Api
│ │ ├── Playground.Api.http
│ │ ├── appsettings.Development.json
│ │ ├── Requests
│ │ │ ├── Identity
│ │ │ │ └── identity-token.http
│ │ │ └── root-and-health.http
│ │ └── Properties
│ │ │ └── launchSettings.json
│ └── Migrations.PostgreSQL
│ │ └── Migrations.PostgreSQL.csproj
├── Modules
│ ├── Auditing
│ │ ├── Modules.Auditing
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── Infrastructure
│ │ │ │ ├── Http
│ │ │ │ │ ├── HttpContextRoutingExtensions.cs
│ │ │ │ │ └── ContentTypeHelper.cs
│ │ │ │ └── Serialization
│ │ │ │ │ └── SystemTextJsonAuditSerializer.cs
│ │ │ ├── Modules.Auditing.csproj
│ │ │ ├── Features
│ │ │ │ └── v1
│ │ │ │ │ ├── GetAuditSummary
│ │ │ │ │ └── GetAuditSummaryQueryValidator.cs
│ │ │ │ │ ├── GetSecurityAudits
│ │ │ │ │ └── GetSecurityAuditsQueryValidator.cs
│ │ │ │ │ ├── GetExceptionAudits
│ │ │ │ │ └── GetExceptionAuditsQueryValidator.cs
│ │ │ │ │ ├── GetAuditsByTrace
│ │ │ │ │ └── GetAuditsByTraceQueryValidator.cs
│ │ │ │ │ ├── GetAuditsByCorrelation
│ │ │ │ │ └── GetAuditsByCorrelationQueryValidator.cs
│ │ │ │ │ ├── GetAudits
│ │ │ │ │ ├── GetAuditsQueryValidator.cs
│ │ │ │ │ └── GetAuditsEndpoint.cs
│ │ │ │ │ └── GetAuditById
│ │ │ │ │ └── GetAuditByIdEndpoint.cs
│ │ │ ├── Persistence
│ │ │ │ ├── AuditRecord.cs
│ │ │ │ ├── AuditDbInitializer.cs
│ │ │ │ ├── AuditRecordConfiguration.cs
│ │ │ │ └── AuditDbContext.cs
│ │ │ └── Core
│ │ │ │ └── AuditingConfigurator.cs
│ │ └── Modules.Auditing.Contracts
│ │ │ ├── AuditingContractsMarker.cs
│ │ │ ├── v1
│ │ │ ├── GetAuditById
│ │ │ │ └── GetAuditByIdQuery.cs
│ │ │ ├── GetAuditSummary
│ │ │ │ └── GetAuditSummaryQuery.cs
│ │ │ ├── GetAuditsByTrace
│ │ │ │ └── GetAuditsByTraceQuery.cs
│ │ │ ├── GetAuditsByCorrelation
│ │ │ │ └── GetAuditsByCorrelationQuery.cs
│ │ │ ├── GetSecurityAudits
│ │ │ │ └── GetSecurityAuditsQuery.cs
│ │ │ ├── GetExceptionAudits
│ │ │ │ └── GetExceptionAuditsQuery.cs
│ │ │ └── GetAudits
│ │ │ │ └── GetAuditsQuery.cs
│ │ │ ├── IAuditMaskingService.cs
│ │ │ ├── IAuditSerializer.cs
│ │ │ ├── IAuditMutatingEnricher.cs
│ │ │ ├── IAuditSink.cs
│ │ │ ├── ExceptionEventPayload.cs
│ │ │ ├── SecurityEventPayload.cs
│ │ │ ├── IAuditEnricher.cs
│ │ │ ├── PropertyChange.cs
│ │ │ ├── ExceptionSeverityClassifier.cs
│ │ │ ├── EntityChangeEventPayload.cs
│ │ │ ├── ActivityEventPayload.cs
│ │ │ ├── Modules.Auditing.Contracts.csproj
│ │ │ ├── IAuditPublisher.cs
│ │ │ ├── ISecurityAudit.cs
│ │ │ ├── Dtos
│ │ │ ├── AuditSummaryAggregateDto.cs
│ │ │ ├── AuditSummaryDto.cs
│ │ │ └── AuditDetailDto.cs
│ │ │ └── AuditHttpOptions.cs
│ ├── Identity
│ │ ├── Modules.Identity
│ │ │ ├── AssemblyInfo.cs
│ │ │ ├── AuthenticationConstants.cs
│ │ │ ├── IRequiredPermissionMetadata.cs
│ │ │ ├── Authorization
│ │ │ │ └── PermissionAuthorizationRequirement.cs
│ │ │ ├── Features
│ │ │ │ └── v1
│ │ │ │ │ ├── RoleClaims
│ │ │ │ │ └── FshRoleClaim.cs
│ │ │ │ │ ├── Roles
│ │ │ │ │ ├── UpsertRole
│ │ │ │ │ │ ├── UpsertRoleCommandValidator.cs
│ │ │ │ │ │ ├── UpsertRoleCommandHandler.cs
│ │ │ │ │ │ └── CreateOrUpdateRoleEndpoint.cs
│ │ │ │ │ ├── FshRole.cs
│ │ │ │ │ ├── UpdateRolePermissions
│ │ │ │ │ │ ├── UpdatePermissionsCommandValidator.cs
│ │ │ │ │ │ └── UpdatePermissionsCommandHandler.cs
│ │ │ │ │ ├── GetRoles
│ │ │ │ │ │ ├── GetRolesQueryHandler.cs
│ │ │ │ │ │ └── GetRolesEndpoint.cs
│ │ │ │ │ ├── GetRoleById
│ │ │ │ │ │ ├── GetRoleByIdQueryHandler.cs
│ │ │ │ │ │ └── GetRoleByIdEndpoint.cs
│ │ │ │ │ ├── DeleteRole
│ │ │ │ │ │ ├── DeleteRoleCommandHandler.cs
│ │ │ │ │ │ └── DeleteRoleEndpoint.cs
│ │ │ │ │ └── GetRoleWithPermissions
│ │ │ │ │ │ ├── GetRoleWithPermissionsQueryHandler.cs
│ │ │ │ │ │ └── GetRolePermissionsEndpoint.cs
│ │ │ │ │ ├── Users
│ │ │ │ │ ├── PasswordHistory
│ │ │ │ │ │ └── PasswordHistory.cs
│ │ │ │ │ ├── ForgotPassword
│ │ │ │ │ │ └── ForgotPasswordCommandValidator.cs
│ │ │ │ │ ├── ResetPassword
│ │ │ │ │ │ ├── ResetPasswordCommandValidator.cs
│ │ │ │ │ │ └── ResetPasswordCommandHandler.cs
│ │ │ │ │ ├── AssignUserRoles
│ │ │ │ │ │ └── AssignUserRolesCommandHandler.cs
│ │ │ │ │ ├── GetUsers
│ │ │ │ │ │ ├── GetUsersQueryHandler.cs
│ │ │ │ │ │ └── GetUsersListEndpoint.cs
│ │ │ │ │ ├── DeleteUser
│ │ │ │ │ │ ├── DeleteUserCommandHandler.cs
│ │ │ │ │ │ └── DeleteUserEndpoint.cs
│ │ │ │ │ ├── GetUserById
│ │ │ │ │ │ ├── GetUserByIdQueryHandler.cs
│ │ │ │ │ │ └── GetUserByIdEndpoint.cs
│ │ │ │ │ ├── SearchUsers
│ │ │ │ │ │ └── SearchUsersQueryValidator.cs
│ │ │ │ │ ├── ConfirmEmail
│ │ │ │ │ │ ├── ConfirmEmailCommandHandler.cs
│ │ │ │ │ │ └── ConfirmEmailEndpoint.cs
│ │ │ │ │ ├── GetUserRoles
│ │ │ │ │ │ ├── GetUserRolesQueryHandler.cs
│ │ │ │ │ │ └── GetUserRolesEndpoint.cs
│ │ │ │ │ ├── GetUserProfile
│ │ │ │ │ │ └── GetCurrentUserProfileQueryHandler.cs
│ │ │ │ │ ├── GetUserPermissions
│ │ │ │ │ │ └── GetCurrentUserPermissionsQueryHandler.cs
│ │ │ │ │ ├── UserImageValidator.cs
│ │ │ │ │ ├── FshUser.cs
│ │ │ │ │ ├── ChangePassword
│ │ │ │ │ │ └── ChangePasswordEndpoint.cs
│ │ │ │ │ └── UpdateUser
│ │ │ │ │ │ └── UpdateUserCommandHandler.cs
│ │ │ │ │ ├── Tokens
│ │ │ │ │ ├── RefreshToken
│ │ │ │ │ │ └── RefreshTokenCommandValidator.cs
│ │ │ │ │ └── TokenGeneration
│ │ │ │ │ │ └── GenerateTokenCommandValidator.cs
│ │ │ │ │ └── Sessions
│ │ │ │ │ ├── GetUserSessions
│ │ │ │ │ └── GetUserSessionsQueryHandler.cs
│ │ │ │ │ └── GetMySessions
│ │ │ │ │ ├── GetMySessionsEndpoint.cs
│ │ │ │ │ └── GetMySessionsQueryHandler.cs
│ │ │ ├── IdentityModuleConstants.cs
│ │ │ ├── Data
│ │ │ │ └── PasswordPolicyOptions.cs
│ │ │ └── IdentityMetrics.cs
│ │ └── Modules.Identity.Contracts
│ │ │ ├── DTOs
│ │ │ ├── TokenDto.cs
│ │ │ ├── TokenResponse.cs
│ │ │ ├── UserRoleDto.cs
│ │ │ ├── RoleDto.cs
│ │ │ ├── UserDto.cs
│ │ │ └── UserSessionDto.cs
│ │ │ ├── v1
│ │ │ ├── Users
│ │ │ │ ├── RegisterUser
│ │ │ │ │ ├── RegisterUserResponse.cs
│ │ │ │ │ └── RegisterUserCommand.cs
│ │ │ │ ├── AssignUserRoles
│ │ │ │ │ ├── AssignUserRolesCommandResponse.cs
│ │ │ │ │ └── AssignUserRolesCommand.cs
│ │ │ │ ├── DeleteUser
│ │ │ │ │ └── DeleteUserCommand.cs
│ │ │ │ ├── GetUser
│ │ │ │ │ └── GetUserQuery.cs
│ │ │ │ ├── GetUsers
│ │ │ │ │ └── GetUsersQuery.cs
│ │ │ │ ├── ConfirmEmail
│ │ │ │ │ └── ConfirmEmailCommand.cs
│ │ │ │ ├── GetUserPermissions
│ │ │ │ │ └── GetCurrentUserPermissionsQuery.cs
│ │ │ │ ├── ForgotPassword
│ │ │ │ │ └── ForgotPasswordCommand.cs
│ │ │ │ ├── GetUserRoles
│ │ │ │ │ └── GetUserRolesQuery.cs
│ │ │ │ ├── GetUserProfile
│ │ │ │ │ └── GetCurrentUserProfileQuery.cs
│ │ │ │ ├── ToggleUserStatus
│ │ │ │ │ └── ToggleUserStatusCommand.cs
│ │ │ │ ├── ResetPassword
│ │ │ │ │ └── ResetPasswordCommand.cs
│ │ │ │ ├── UpdateUser
│ │ │ │ │ └── UpdateUserCommand.cs
│ │ │ │ ├── ChangePassword
│ │ │ │ │ └── ChangePasswordCommand.cs
│ │ │ │ └── SearchUsers
│ │ │ │ │ └── SearchUsersQuery.cs
│ │ │ ├── Roles
│ │ │ │ ├── DeleteRole
│ │ │ │ │ └── DeleteRoleCommand.cs
│ │ │ │ ├── GetRole
│ │ │ │ │ └── GetRoleQuery.cs
│ │ │ │ ├── GetRoles
│ │ │ │ │ └── GetRolesQuery.cs
│ │ │ │ ├── GetRoleWithPermissions
│ │ │ │ │ └── GetRoleWithPermissionsQuery.cs
│ │ │ │ ├── UpsertRole
│ │ │ │ │ └── UpsertRoleCommand.cs
│ │ │ │ └── UpdatePermissions
│ │ │ │ │ └── UpdatePermissionsCommand.cs
│ │ │ ├── Sessions
│ │ │ │ ├── RevokeSession
│ │ │ │ │ └── RevokeSessionCommand.cs
│ │ │ │ ├── RevokeAllSessions
│ │ │ │ │ └── RevokeAllSessionsCommand.cs
│ │ │ │ ├── GetMySessions
│ │ │ │ │ └── GetMySessionsQuery.cs
│ │ │ │ ├── AdminRevokeAllSessions
│ │ │ │ │ └── AdminRevokeAllSessionsCommand.cs
│ │ │ │ ├── AdminRevokeSession
│ │ │ │ │ └── AdminRevokeSessionCommand.cs
│ │ │ │ └── GetUserSessions
│ │ │ │ │ └── GetUserSessionsQuery.cs
│ │ │ └── Tokens
│ │ │ │ ├── RefreshToken
│ │ │ │ ├── RefreshTokenCommand.cs
│ │ │ │ └── RefreshTokenCommandResponse.cs
│ │ │ │ └── TokenGeneration
│ │ │ │ └── GenerateTokenCommand.cs
│ │ │ ├── IdentityContractsMarker.cs
│ │ │ ├── Services
│ │ │ ├── ITokenService.cs
│ │ │ └── IRoleService.cs
│ │ │ ├── Events
│ │ │ ├── UserRegisteredIntegrationEvent.cs
│ │ │ └── TokenGeneratedIntegrationEvent.cs
│ │ │ └── Modules.Identity.Contracts.csproj
│ └── Multitenancy
│ │ ├── Modules.Multitenancy
│ │ ├── AssemblyInfo.cs
│ │ ├── Provisioning
│ │ │ ├── TenantProvisioningStatus.cs
│ │ │ └── TenantProvisioningStepName.cs
│ │ ├── Features
│ │ │ └── v1
│ │ │ │ ├── ChangeTenantActivation
│ │ │ │ └── ChangeTenantActivationCommandValidator.cs
│ │ │ │ ├── UpgradeTenant
│ │ │ │ ├── UpgradeTenantCommandValidator.cs
│ │ │ │ └── UpgradeTenantCommandHandler.cs
│ │ │ │ ├── GetTenantTheme
│ │ │ │ └── GetTenantThemeQueryHandler.cs
│ │ │ │ ├── GetTenantStatus
│ │ │ │ └── GetTenantStatusQueryHandler.cs
│ │ │ │ ├── GetTenants
│ │ │ │ └── GetTenantsQueryHandler.cs
│ │ │ │ ├── TenantProvisioning
│ │ │ │ └── GetTenantProvisioningStatus
│ │ │ │ │ └── GetTenantProvisioningStatusQueryHandler.cs
│ │ │ │ ├── ResetTenantTheme
│ │ │ │ └── ResetTenantThemeCommandHandler.cs
│ │ │ │ └── UpdateTenantTheme
│ │ │ │ └── UpdateTenantThemeCommandHandler.cs
│ │ ├── Data
│ │ │ └── Configurations
│ │ │ │ ├── AppTenantInfoConfiguration.cs
│ │ │ │ ├── TenantProvisioningStepConfiguration.cs
│ │ │ │ └── TenantProvisioningConfiguration.cs
│ │ ├── Extensions.cs
│ │ └── MultitenancyOptions.cs
│ │ └── Modules.Multitenancy.Contracts
│ │ ├── v1
│ │ ├── ResetTenantTheme
│ │ │ └── ResetTenantThemeCommand.cs
│ │ ├── UpgradeTenant
│ │ │ ├── UpgradeTenantCommandResponse.cs
│ │ │ └── UpgradeTenantCommand.cs
│ │ ├── CreateTenant
│ │ │ ├── CreateTenantCommandResponse.cs
│ │ │ └── CreateTenantCommand.cs
│ │ ├── GetTenantTheme
│ │ │ └── GetTenantThemeQuery.cs
│ │ ├── GetTenantStatus
│ │ │ └── GetTenantStatusQuery.cs
│ │ ├── UpdateTenantTheme
│ │ │ └── UpdateTenantThemeCommand.cs
│ │ ├── GetTenantMigrations
│ │ │ └── GetTenantMigrationsQuery.cs
│ │ ├── TenantProvisioning
│ │ │ ├── RetryTenantProvisioningCommand.cs
│ │ │ └── GetTenantProvisioningStatusQuery.cs
│ │ ├── ChangeTenantActivation
│ │ │ └── ChangeTenantActivationCommand.cs
│ │ └── GetTenants
│ │ │ └── GetTenantsQuery.cs
│ │ ├── MultitenancyContractsMarker.cs
│ │ ├── Dtos
│ │ ├── TenantLifecycleResultDto.cs
│ │ ├── TenantDto.cs
│ │ ├── TenantStatusDto.cs
│ │ ├── TenantProvisioningStatusDto.cs
│ │ └── TenantMigrationStatusDto.cs
│ │ └── Modules.Multitenancy.Contracts.csproj
├── Tools
│ └── CLI
│ │ └── Program.cs
└── Tests
│ └── Multitenacy.Tests
│ └── Multitenacy.Tests.csproj
├── .template.config
├── icon.png
└── ide.host.json
├── terraform
├── modules
│ ├── alb
│ │ ├── versions.tf
│ │ └── outputs.tf
│ ├── network
│ │ └── versions.tf
│ ├── s3_bucket
│ │ └── versions.tf
│ ├── ecs_cluster
│ │ ├── versions.tf
│ │ └── outputs.tf
│ ├── ecs_service
│ │ └── versions.tf
│ ├── elasticache_redis
│ │ └── versions.tf
│ └── rds_postgres
│ │ └── versions.tf
└── apps
│ ├── playground
│ ├── app_stack
│ │ └── versions.tf
│ ├── envs
│ │ ├── dev
│ │ │ └── us-east-1
│ │ │ │ └── backend.tf
│ │ ├── prod
│ │ │ └── us-east-1
│ │ │ │ ├── backend.tf
│ │ │ │ └── prod.us-east-1.tfvars
│ │ └── staging
│ │ │ └── us-east-1
│ │ │ ├── backend.tf
│ │ │ └── staging.us-east-1.tfvars
│ └── README.md
│ └── restaurantpos
│ └── README.md
├── .config
└── dotnet-tools.json
└── scripts
└── openapi
├── check-openapi-drift.ps1
└── generate-api-clients.ps1
/src/BuildingBlocks/Blazor.UI/wwwroot/fsh.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using MudBlazor;
2 | global using MudBlazor.Services;
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/IFshWeb.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web;
2 | public interface IFshWeb
3 | {
4 | }
5 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/IFshCore.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core;
2 | public interface IFshCore
3 | {
4 | }
5 |
--------------------------------------------------------------------------------
/.template.config/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackhero/dotnet-starter-kit/HEAD/.template.config/icon.png
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Title/FshPageTitle.razor:
--------------------------------------------------------------------------------
1 |
FshPageTitle
2 |
3 | @code {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/src/Playground/FSH.Playground.AppHost/.aspire/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "appHostPath": "../FSH.Playground.AppHost.csproj"
3 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/FshScripts.razor:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.template.config/ide.host.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/vs-2017.3.host",
3 | "order": 0,
4 | "icon": "icon.png"
5 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/IEntity.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface IEntity
3 | {
4 | TId Id { get; }
5 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/IHasTenant.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface IHasTenant
3 | {
4 | string TenantId { get; }
5 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Origin/OriginOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.Origin;
2 |
3 | public class OriginOptions
4 | {
5 | public Uri? OriginUrl { get; set; }
6 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Web.Modules;
2 |
3 | [assembly: FshModule(typeof(FSH.Modules.Auditing.AuditingModule), 300)]
4 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Web.Modules;
2 |
3 | [assembly: FshModule(typeof(FSH.Modules.Identity.IdentityModule), 100)]
4 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fullstackhero/dotnet-starter-kit/HEAD/src/Playground/Playground.Blazor/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Layouts/FshBaseLayout.razor.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Blazor.UI.Components.Layouts;
2 |
3 | public partial class FshBaseLayout
4 | {
5 | }
6 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Web.Modules;
2 |
3 | [assembly: FshModule(typeof(FSH.Modules.Multitenancy.MultitenancyModule), 200)]
4 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Multitenancy/IAppTenantInfo.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Multitenancy;
2 |
3 | public interface IAppTenantInfo
4 | {
5 | string? ConnectionString { get; set; }
6 | }
--------------------------------------------------------------------------------
/src/Playground/Playground.Api/Playground.Api.http:
--------------------------------------------------------------------------------
1 | @Playground.Api_HostAddress = http://localhost:5014
2 |
3 | GET {{Playground.Api_HostAddress}}/weatherforecast/
4 | Accept: application/json
5 |
6 | ###
7 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Services/Api/ApiClients.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Playground.Blazor.Services.Api;
2 |
3 | internal static class ApiClients
4 | {
5 | public const string FSH = "fshapi";
6 | }
7 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Mailing/Services/IMailService.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Mailing.Services;
2 |
3 | public interface IMailService
4 | {
5 | Task SendAsync(MailRequest request, CancellationToken ct);
6 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/TokenDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | public record TokenDto(string Token, string RefreshToken, DateTime RefreshTokenExpiryTime);
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/RegisterUser/RegisterUserResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.v1.Users.RegisterUser;
2 |
3 | public record RegisterUserResponse(string UserId);
--------------------------------------------------------------------------------
/src/Playground/FSH.Playground.AppHost/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/AuthenticationConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity;
2 |
3 | public static class AuthenticationConstants
4 | {
5 | public const string AuthenticationScheme = "Bearer";
6 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/IRequiredPermissionMetadata.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity;
2 |
3 | public interface IRequiredPermissionMetadata
4 | {
5 | HashSet RequiredPermissions { get; }
6 | }
7 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Common/QueryStringKeys.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Common;
2 | public static class QueryStringKeys
3 | {
4 | public const string Code = "code";
5 | public const string UserId = "userId";
6 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/AggregateRoot.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public abstract class AggregateRoot : BaseEntity
3 | {
4 | // Put aggregate-wide behaviors/helpers here if needed
5 | }
6 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/Layout/EmptyLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 |
4 |
5 |
6 |
7 |
8 | @Body
9 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/IHasDomainEvents.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface IHasDomainEvents
3 | {
4 | IReadOnlyCollection DomainEvents { get; }
5 | void ClearDomainEvents();
6 | }
--------------------------------------------------------------------------------
/terraform/modules/alb/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/IConnectionStringValidator.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Persistence;
2 |
3 | public interface IConnectionStringValidator
4 | {
5 | bool TryValidate(string connectionString, string? dbProvider = null);
6 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Modules/IModuleConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.Modules;
2 |
3 | public interface IModuleConstants
4 | {
5 | string ModuleId { get; }
6 | string ModuleName { get; }
7 | string ApiPrefix { get; }
8 | }
--------------------------------------------------------------------------------
/terraform/modules/network/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/terraform/modules/s3_bucket/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/Pages/Home.razor:
--------------------------------------------------------------------------------
1 | @page "/welcome"
2 | @inherits ComponentBase
3 |
4 |
6 |
--------------------------------------------------------------------------------
/terraform/modules/ecs_cluster/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/terraform/modules/ecs_service/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/ISoftDeletable.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface ISoftDeletable
3 | {
4 | bool IsDeleted { get; }
5 | DateTimeOffset? DeletedOnUtc { get; }
6 | string? DeletedBy { get; }
7 | }
8 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/AuditingPermissionConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Identity;
2 |
3 | public static class AuditingPermissionConstants
4 | {
5 | public const string View = "Permissions.AuditTrails.View";
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/AuditingContractsMarker.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | // Marker type for contract assembly scanning (Mediator, etc.)
4 | public sealed class AuditingContractsMarker
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/IdentityContractsMarker.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts;
2 |
3 | // Marker type for contract assembly scanning (Mediator, etc.)
4 | public sealed class IdentityContractsMarker
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/AssignUserRoles/AssignUserRolesCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.v1.Users.AssignUserRoles;
2 |
3 | public sealed record AssignUserRolesCommandResponse(string Result);
--------------------------------------------------------------------------------
/terraform/apps/playground/app_stack/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/terraform/modules/elasticache_redis/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Playground/FSH.Playground.AppHost/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning",
6 | "Aspire.Hosting.Dcp": "Warning"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/IDbInitializer.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Persistence;
2 |
3 | public interface IDbInitializer
4 | {
5 | Task MigrateAsync(CancellationToken cancellationToken);
6 | Task SeedAsync(CancellationToken cancellationToken);
7 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/DeleteRole/DeleteRoleCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Roles.DeleteRole;
4 |
5 | public sealed record DeleteRoleCommand(string Id) : ICommand;
6 |
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/DeleteUser/DeleteUserCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.DeleteUser;
4 |
5 | public sealed record DeleteUserCommand(string Id) : ICommand;
6 |
7 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/ResetTenantTheme/ResetTenantThemeCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Multitenancy.Contracts.v1.ResetTenantTheme;
4 |
5 | public sealed record ResetTenantThemeCommand : ICommand;
6 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/UpgradeTenant/UpgradeTenantCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.v1.UpgradeTenant;
2 |
3 | public sealed record UpgradeTenantCommandResponse(DateTime NewValidity, string Tenant);
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "nswag.consolecore": {
6 | "version": "14.6.3",
7 | "commands": [
8 | "nswag"
9 | ],
10 | "rollForward": false
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Authorization/PermissionAuthorizationRequirement.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Authorization;
2 |
3 | namespace FSH.Modules.Identity.Authorization;
4 |
5 | public class PermissionAuthorizationRequirement : IAuthorizationRequirement;
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/MultitenancyContractsMarker.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts;
2 |
3 | // Marker type for contract assembly scanning (Mediator, etc.)
4 | public sealed class MultitenancyContractsMarker
5 | {
6 | }
7 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/IDomainEvent.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface IDomainEvent
3 | {
4 | Guid EventId { get; }
5 | DateTimeOffset OccurredOnUtc { get; }
6 | string? CorrelationId { get; }
7 | string? TenantId { get; }
8 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/RevokeSession/RevokeSessionCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.RevokeSession;
4 |
5 | public sealed record RevokeSessionCommand(Guid SessionId) : ICommand;
6 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Context/ICurrentUserInitializer.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 |
3 | namespace FSH.Framework.Core.Context;
4 | public interface ICurrentUserInitializer
5 | {
6 | void SetCurrentUser(ClaimsPrincipal user);
7 |
8 | void SetCurrentUserId(string userId);
9 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/GetUser/GetUserQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.GetUser;
5 |
6 | public sealed record GetUserQuery(string Id) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/GetUsers/GetUsersQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.GetUsers;
5 |
6 | public sealed record GetUsersQuery : IQuery>;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Provisioning/TenantProvisioningStatus.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Provisioning;
2 |
3 | public enum TenantProvisioningStatus
4 | {
5 | Pending = 0,
6 | Running = 1,
7 | Completed = 2,
8 | Failed = 3
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/TokenResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | public sealed record TokenResponse(
4 | string AccessToken,
5 | string RefreshToken,
6 | DateTime RefreshTokenExpiresAt,
7 | DateTime AccessTokenExpiresAt);
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/GetRole/GetRoleQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Roles.GetRole;
5 |
6 | public sealed record GetRoleQuery(string Id) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Jobs/HangfireOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Jobs;
2 |
3 | public class HangfireOptions
4 | {
5 | public string UserName { get; set; } = "admin";
6 | public string Password { get; set; } = "Secure1234!Me";
7 | public string Route { get; set; } = "/jobs";
8 | }
9 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/GetRoles/GetRolesQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Roles.GetRoles;
5 |
6 | public sealed record GetRolesQuery : IQuery>;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/ConfirmEmail/ConfirmEmailCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.ConfirmEmail;
4 |
5 | public sealed record ConfirmEmailCommand(string UserId, string Code, string Tenant) : ICommand;
6 |
7 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Provisioning/TenantProvisioningStepName.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Provisioning;
2 |
3 | public enum TenantProvisioningStepName
4 | {
5 | Database = 1,
6 | Migrations = 2,
7 | Seeding = 3,
8 | CacheWarm = 4
9 | }
10 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/IAuditableEntity.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public interface IAuditableEntity
3 | {
4 | DateTimeOffset CreatedOnUtc { get; }
5 | string? CreatedBy { get; }
6 | DateTimeOffset? LastModifiedOnUtc { get; }
7 | string? LastModifiedBy { get; }
8 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetAuditById/GetAuditByIdQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Auditing.Contracts.v1.GetAuditById;
5 |
6 | public sealed record GetAuditByIdQuery(Guid Id) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/RevokeAllSessions/RevokeAllSessionsCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.RevokeAllSessions;
4 |
5 | public sealed record RevokeAllSessionsCommand(Guid? ExceptSessionId = null) : ICommand;
6 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "Api": {
10 | "BaseUrl": "https://localhost:7030"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Storage/DTOs/FileUploadRequest.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Storage.DTOs;
2 |
3 | public class FileUploadRequest
4 | {
5 | public string FileName { get; set; } = default!;
6 | public string ContentType { get; set; } = default!;
7 | public List Data { get; set; } = [];
8 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Tokens/RefreshToken/RefreshTokenCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Tokens.RefreshToken;
4 |
5 | public record RefreshTokenCommand(string Token, string RefreshToken)
6 | : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/CreateTenant/CreateTenantCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.v1.CreateTenant;
2 |
3 | public sealed record CreateTenantCommandResponse(
4 | string Id,
5 | string ProvisioningCorrelationId,
6 | string Status);
7 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/appsettings.Production.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "ui.example.com",
9 | "Api": {
10 | "BaseUrl": ""
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Tokens/RefreshToken/RefreshTokenCommandResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.v1.Tokens.RefreshToken;
2 |
3 | public sealed record RefreshTokenCommandResponse(
4 | string Token,
5 | string RefreshToken,
6 | DateTime RefreshTokenExpiryTime);
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/GetUserPermissions/GetCurrentUserPermissionsQuery.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.GetUserPermissions;
4 |
5 | public sealed record GetCurrentUserPermissionsQuery(string UserId) : IQuery?>;
6 |
7 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/GetTenantTheme/GetTenantThemeQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.GetTenantTheme;
5 |
6 | public sealed record GetTenantThemeQuery : IQuery;
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/GetMySessions/GetMySessionsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.GetMySessions;
5 |
6 | public sealed record GetMySessionsQuery : IQuery>;
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/ForgotPassword/ForgotPasswordCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.ForgotPassword;
4 |
5 | public class ForgotPasswordCommand : ICommand
6 | {
7 | public string Email { get; set; } = default!;
8 | }
9 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/GetUserRoles/GetUserRolesQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.GetUserRoles;
5 |
6 | public sealed record GetUserRolesQuery(string UserId) : IQuery>;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/UpgradeTenant/UpgradeTenantCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Multitenancy.Contracts.v1.UpgradeTenant;
4 |
5 | public sealed record UpgradeTenantCommand(string Tenant, DateTime ExtendedExpiryDate)
6 | : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/AdminRevokeAllSessions/AdminRevokeAllSessionsCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.AdminRevokeAllSessions;
4 |
5 | public sealed record AdminRevokeAllSessionsCommand(Guid UserId, string? Reason = null) : ICommand;
6 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/AdminRevokeSession/AdminRevokeSessionCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.AdminRevokeSession;
4 |
5 | public sealed record AdminRevokeSessionCommand(Guid UserId, Guid SessionId, string? Reason = null) : ICommand;
6 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/UserRoleDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | public class UserRoleDto
4 | {
5 | public string? RoleId { get; set; }
6 | public string? RoleName { get; set; }
7 | public string? Description { get; set; }
8 | public bool Enabled { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Sessions/GetUserSessions/GetUserSessionsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Sessions.GetUserSessions;
5 |
6 | public sealed record GetUserSessionsQuery(Guid UserId) : IQuery>;
7 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/GetUserProfile/GetCurrentUserProfileQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.GetUserProfile;
5 |
6 | public sealed record GetCurrentUserProfileQuery(string UserId) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/GetTenantStatus/GetTenantStatusQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.GetTenantStatus;
5 |
6 | public sealed record GetTenantStatusQuery(string TenantId) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/UpdateTenantTheme/UpdateTenantThemeCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.UpdateTenantTheme;
5 |
6 | public sealed record UpdateTenantThemeCommand(TenantThemeDto Theme) : ICommand;
7 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Api/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "CorsOptions": {
9 | "AllowAll": true
10 | },
11 | "OpenApiOptions": {
12 | "Enabled": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/RateLimiting/FixedWindowPolicyOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.RateLimiting;
2 |
3 | public sealed class FixedWindowPolicyOptions
4 | {
5 | public int PermitLimit { get; set; } = 100;
6 | public int WindowSeconds { get; set; } = 60;
7 | public int QueueLimit { get; set; } = 0;
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditMaskingService.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Masks or hashes sensitive fields before persistence or externalization.
5 | ///
6 | public interface IAuditMaskingService
7 | {
8 | object ApplyMasking(object payload);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/GetRoleWithPermissions/GetRoleWithPermissionsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Roles.GetRoleWithPermissions;
5 |
6 | public sealed record GetRoleWithPermissionsQuery(string Id) : IQuery;
7 |
8 |
--------------------------------------------------------------------------------
/terraform/modules/rds_postgres/versions.tf:
--------------------------------------------------------------------------------
1 | terraform {
2 | required_version = ">= 1.14.0"
3 |
4 | required_providers {
5 | aws = {
6 | source = "hashicorp/aws"
7 | version = ">= 5.80.0"
8 | }
9 | random = {
10 | source = "hashicorp/random"
11 | version = ">= 3.6.0"
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/RoleClaims/FshRoleClaim.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 |
3 | namespace FSH.Modules.Identity.Features.v1.RoleClaims;
4 |
5 | public class FshRoleClaim : IdentityRoleClaim
6 | {
7 | public string? CreatedBy { get; init; }
8 | public DateTimeOffset CreatedOn { get; init; }
9 | }
--------------------------------------------------------------------------------
/src/Playground/Playground.Api/Requests/Identity/identity-token.http:
--------------------------------------------------------------------------------
1 | @baseUrl = https://localhost:7030
2 | @tenant = root
3 |
4 | ### Issue JWT token
5 | POST {{baseUrl}}/api/v1/identity/token
6 | Content-Type: application/json
7 | tenant: {{tenant}}
8 |
9 | {
10 | "email": "admin@root.com",
11 | "password": "123Pa$$word!"
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Abstractions/IAppUser.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Abstractions;
2 | public interface IAppUser
3 | {
4 | string? FirstName { get; }
5 | string? LastName { get; }
6 | Uri? ImageUrl { get; }
7 | bool IsActive { get; }
8 | string? RefreshToken { get; }
9 | DateTime RefreshTokenExpiryTime { get; }
10 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Deterministic JSON serialization for payloads (camelCase, enum-as-string, stable output).
5 | ///
6 | public interface IAuditSerializer
7 | {
8 | string SerializePayload(object payload);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Tokens/TokenGeneration/GenerateTokenCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Tokens.TokenGeneration;
5 |
6 | public record GenerateTokenCommand(
7 | string Email,
8 | string Password)
9 | : ICommand;
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Cors/CorsOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.Cors;
2 |
3 | public sealed class CorsOptions
4 | {
5 | public bool AllowAll { get; init; } = true;
6 | public string[] AllowedOrigins { get; init; } = [];
7 | public string[] AllowedHeaders { get; init; } = ["*"];
8 | public string[] AllowedMethods { get; init; } = ["*"];
9 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Layouts/FshDrawerHeader.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
3 |
4 |
5 | @Title
6 |
7 |
8 |
9 | @code {
10 | [Parameter]
11 | public string Title { get; set; } = "Fsh Playground";
12 | }
13 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/ToggleUserStatus/ToggleUserStatusCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.ToggleUserStatus;
4 |
5 | public class ToggleUserStatusCommand : ICommand
6 | {
7 | public bool ActivateUser { get; set; }
8 | public string? UserId { get; set; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/GetTenantMigrations/GetTenantMigrationsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.GetTenantMigrations;
5 |
6 | public sealed record GetTenantMigrationsQuery : IQuery>;
7 |
8 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/FshHead.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditMutatingEnricher.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Enricher that can return a modified event (e.g., fill missing fields, mask payload).
5 | ///
6 | public interface IAuditMutatingEnricher
7 | {
8 | AuditEnvelope Enrich(AuditEnvelope envelope);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/RoleDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | public class RoleDto
4 | {
5 | public string Id { get; set; } = default!;
6 | public string Name { get; set; } = default!;
7 | public string? Description { get; set; }
8 | public IReadOnlyCollection? Permissions { get; set; }
9 | }
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/TenantProvisioning/RetryTenantProvisioningCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.TenantProvisioning;
5 |
6 | public sealed record RetryTenantProvisioningCommand(string TenantId) : ICommand;
7 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/TenantProvisioning/GetTenantProvisioningStatusQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.TenantProvisioning;
5 |
6 | public sealed record GetTenantProvisioningStatusQuery(string TenantId) : IQuery;
7 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Api/Requests/root-and-health.http:
--------------------------------------------------------------------------------
1 | @baseUrl = https://localhost:7030
2 |
3 | ### Root hello
4 | GET {{baseUrl}}/
5 | Accept: application/json
6 |
7 | ### Liveness probe
8 | GET {{baseUrl}}/health/live
9 | Accept: application/json
10 |
11 | ### Readiness probe (includes DB checks)
12 | GET {{baseUrl}}/health/ready
13 | Accept: application/json
14 |
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Storage/S3/S3StorageOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Storage.S3;
2 |
3 | public sealed class S3StorageOptions
4 | {
5 | public string? Bucket { get; set; }
6 | public string? Region { get; set; }
7 | public string? Prefix { get; set; }
8 | public bool PublicRead { get; set; } = true;
9 | public string? PublicBaseUrl { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Components.Web
2 | @using Microsoft.AspNetCore.Components.Forms
3 | @using MudBlazor
4 | @using FSH.Framework.Blazor.UI.Components.Base
5 | @using FSH.Framework.Blazor.UI.Theme
6 | @using FSH.Framework.Blazor.UI.Components.Theme
7 | @using FSH.Framework.Blazor.UI.Components.Page
8 | @using FSH.Framework.Blazor.UI.Components.User
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditSink.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Destination for audit events (e.g., SQL, file, OTLP). Implementations must be efficient and batch-friendly.
5 | ///
6 | public interface IAuditSink
7 | {
8 | Task WriteAsync(IReadOnlyList batch, CancellationToken ct);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/ChangeTenantActivation/ChangeTenantActivationCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Multitenancy.Contracts.v1.ChangeTenantActivation;
5 |
6 | public sealed record ChangeTenantActivationCommand(string TenantId, bool IsActive)
7 | : ICommand;
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/CreateTenant/CreateTenantCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Multitenancy.Contracts.v1.CreateTenant;
4 |
5 | public sealed record CreateTenantCommand(
6 | string Id,
7 | string Name,
8 | string? ConnectionString,
9 | string AdminEmail,
10 | string? Issuer) : ICommand;
--------------------------------------------------------------------------------
/src/BuildingBlocks/Mailing/MailOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Mailing;
2 |
3 | public class MailOptions
4 | {
5 | public string? From { get; set; }
6 |
7 | public string? Host { get; set; }
8 |
9 | public int Port { get; set; }
10 |
11 | public string? UserName { get; set; }
12 |
13 | public string? Password { get; set; }
14 |
15 | public string? DisplayName { get; set; }
16 | }
--------------------------------------------------------------------------------
/terraform/modules/ecs_cluster/outputs.tf:
--------------------------------------------------------------------------------
1 | output "id" {
2 | description = "The ID of the ECS cluster"
3 | value = aws_ecs_cluster.this.id
4 | }
5 |
6 | output "arn" {
7 | description = "The ARN of the ECS cluster"
8 | value = aws_ecs_cluster.this.arn
9 | }
10 |
11 | output "name" {
12 | description = "The name of the ECS cluster"
13 | value = aws_ecs_cluster.this.name
14 | }
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/RateLimiting/RateLimitingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.RateLimiting;
2 |
3 | public sealed class RateLimitingOptions
4 | {
5 | public bool Enabled { get; set; } = true;
6 | public FixedWindowPolicyOptions Global { get; set; } = new();
7 | public FixedWindowPolicyOptions Auth { get; set; } = new() { PermitLimit = 10, WindowSeconds = 60, QueueLimit = 0 };
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/terraform/apps/playground/envs/dev/us-east-1/backend.tf:
--------------------------------------------------------------------------------
1 | # Terraform 1.10+ uses S3 native locking via use_lockfile.
2 | # DynamoDB is no longer required for state locking.
3 | terraform {
4 | backend "s3" {
5 | bucket = "fsh-state-bucket"
6 | key = "dev/us-east-1/terraform.tfstate"
7 | region = "us-east-1"
8 | encrypt = true
9 | use_lockfile = true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Navigation/FshBreadcrumbs.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 | @code {
4 | [Parameter] public IEnumerable Items { get; set; } = Array.Empty();
5 | [Parameter] public int MaxItems { get; set; } = 4;
6 |
7 | private IReadOnlyList GetItemsList() => Items?.ToList() ?? [];
8 | }
9 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/ExceptionEventPayload.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public sealed record ExceptionEventPayload(
4 | ExceptionArea Area,
5 | string ExceptionType,
6 | string Message,
7 | IReadOnlyList StackTop, // capped frames
8 | IReadOnlyDictionary? Data,
9 | string? RouteOrLocation
10 | );
11 |
--------------------------------------------------------------------------------
/terraform/apps/playground/README.md:
--------------------------------------------------------------------------------
1 | # Playground App Stack
2 | Terraform stack for the Playground API/Blazor app. Uses shared modules from `../../modules`.
3 |
4 | - Env/region stacks live under `envs///` (backend.tf + *.tfvars + main.tf).
5 | - App composition lives under `app_stack/` (wiring ECS services, ALB, RDS, Redis, S3).
6 | - Images are built from GitHub Actions, pushed to ECR, and referenced in tfvars.
7 |
--------------------------------------------------------------------------------
/terraform/apps/playground/envs/prod/us-east-1/backend.tf:
--------------------------------------------------------------------------------
1 | # Terraform 1.10+ uses S3 native locking via use_lockfile.
2 | # DynamoDB is no longer required for state locking.
3 | terraform {
4 | backend "s3" {
5 | bucket = "fsh-state-bucket"
6 | key = "prod/us-east-1/terraform.tfstate"
7 | region = "us-east-1"
8 | encrypt = true
9 | use_lockfile = true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/terraform/apps/playground/envs/staging/us-east-1/backend.tf:
--------------------------------------------------------------------------------
1 | # Terraform 1.10+ uses S3 native locking via use_lockfile.
2 | # DynamoDB is no longer required for state locking.
3 | terraform {
4 | backend "s3" {
5 | bucket = "fsh-state-bucket"
6 | key = "staging/us-east-1/terraform.tfstate"
7 | region = "us-east-1"
8 | encrypt = true
9 | use_lockfile = true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Context/ICurrentUser.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 |
3 | namespace FSH.Framework.Core.Context;
4 | public interface ICurrentUser
5 | {
6 | string? Name { get; }
7 |
8 | Guid GetUserId();
9 |
10 | string? GetUserEmail();
11 |
12 | string? GetTenant();
13 |
14 | bool IsAuthenticated();
15 |
16 | bool IsInRole(string role);
17 |
18 | IEnumerable? GetUserClaims();
19 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Abstractions/IEventSerializer.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing.Abstractions;
2 |
3 | ///
4 | /// Serializes and deserializes integration events for transport and storage (outbox).
5 | ///
6 | public interface IEventSerializer
7 | {
8 | string Serialize(IIntegrationEvent @event);
9 |
10 | IIntegrationEvent? Deserialize(string payload, string eventTypeName);
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Dtos/TenantLifecycleResultDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.Dtos;
2 |
3 | public sealed class TenantLifecycleResultDto
4 | {
5 | public string TenantId { get; set; } = default!;
6 |
7 | public bool IsActive { get; set; }
8 |
9 | public DateTime? ValidUpto { get; set; }
10 |
11 | public string Message { get; set; } = string.Empty;
12 | }
13 |
--------------------------------------------------------------------------------
/terraform/apps/restaurantpos/README.md:
--------------------------------------------------------------------------------
1 | # RestaurantPOS App Stack (skeleton)
2 | Placeholder for a future app using shared modules from `../../modules`.
3 |
4 | - Env/region stacks will live under `envs///` (backend.tf + *.tfvars + main.tf).
5 | - App composition will live under `app_stack/` (ECS services, ALB, DB/cache/S3 as needed).
6 | - Images will come from the RestaurantPOS app Dockerfiles and be referenced in tfvars.
7 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Security/SecurityExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 |
3 | namespace FSH.Framework.Web.Security;
4 |
5 | public static class SecurityExtensions
6 | {
7 | public static IApplicationBuilder UseHeroSecurityHeaders(this IApplicationBuilder app)
8 | {
9 | ArgumentNullException.ThrowIfNull(app);
10 | return app.UseMiddleware();
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/SecurityEventPayload.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public sealed record SecurityEventPayload(
4 | SecurityAction Action,
5 | string? SubjectId,
6 | string? ClientId,
7 | string? AuthMethod, // Password, OIDC, etc.
8 | string? ReasonCode, // InvalidPassword, LockedOut, etc.
9 | IReadOnlyDictionary? ClaimsSnapshot
10 | );
11 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/AssignUserRoles/AssignUserRolesCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.AssignUserRoles;
5 |
6 | public sealed class AssignUserRolesCommand : ICommand
7 | {
8 | public required string UserId { get; init; }
9 | public List UserRoles { get; init; } = new();
10 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/ResetPassword/ResetPasswordCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.ResetPassword;
4 |
5 | public class ResetPasswordCommand : ICommand
6 | {
7 | public string Email { get; set; } = default!;
8 |
9 | public string Password { get; set; } = default!;
10 |
11 | public string Token { get; set; } = default!;
12 | }
13 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/CustomClaims.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Constants;
2 |
3 | public static class CustomClaims
4 | {
5 | public const string Tenant = "tenant";
6 | public const string Fullname = "fullName";
7 | public const string Permission = "permission";
8 | public const string ImageUrl = "image_url";
9 | public const string IpAddress = "ipAddress";
10 | public const string Expiration = "exp";
11 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/UpsertRole/UpsertRoleCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Roles.UpsertRole;
5 |
6 | public class UpsertRoleCommand : ICommand
7 | {
8 | public string Id { get; set; } = default!;
9 | public string Name { get; set; } = default!;
10 | public string? Description { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Modules/IModule.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Routing;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | namespace FSH.Framework.Web.Modules;
5 |
6 | public interface IModule
7 | {
8 | // DI/Options/Health/etc. — don’t depend on ASP.NET types here
9 | void ConfigureServices(IHostApplicationBuilder builder);
10 |
11 | // HTTP wiring — Minimal APIs only
12 | void MapEndpoints(IEndpointRouteBuilder endpoints);
13 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/ClaimConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Constants;
2 |
3 | public static class ClaimConstants
4 | {
5 | public const string Tenant = "tenant";
6 | public const string Fullname = "fullName";
7 | public const string Permission = "permission";
8 | public const string ImageUrl = "image_url";
9 | public const string IpAddress = "ipAddress";
10 | public const string Expiration = "exp";
11 | }
12 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditEnricher.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Hook to augment events before they are published (e.g., add tenant/user/trace, normalize fields, enforce caps).
5 | ///
6 | public interface IAuditEnricher
7 | {
8 | /// Mutate/augment the event instance prior to serialization/publish.
9 | void Enrich(IAuditEvent auditEvent);
10 | }
11 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Storage/Services/IStorageService.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Storage.DTOs;
2 |
3 | namespace FSH.Framework.Storage.Services;
4 |
5 | public interface IStorageService
6 | {
7 | Task UploadAsync(
8 | FileUploadRequest request,
9 | FileType fileType,
10 | CancellationToken cancellationToken = default) where T : class;
11 |
12 | Task RemoveAsync(string path, CancellationToken cancellationToken = default);
13 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/PropertyChange.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// A single property delta for entity-change auditing.
5 | ///
6 | public sealed record PropertyChange(
7 | string Name,
8 | string? DataType, // e.g., "string", "int", "datetime"
9 | object? OldValue,
10 | object? NewValue,
11 | bool IsSensitive // true => value already masked/hashed
12 | );
13 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Abstractions/IIntegrationEventHandler.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing.Abstractions;
2 |
3 | ///
4 | /// Handles a single integration event type.
5 | ///
6 | /// The integration event type.
7 | public interface IIntegrationEventHandler
8 | where TEvent : IIntegrationEvent
9 | {
10 | Task HandleAsync(TEvent @event, CancellationToken ct = default);
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Base/FshComponentBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components;
2 |
3 | namespace FSH.Framework.Blazor.UI.Components.Base;
4 |
5 | public abstract class FshComponentBase : ComponentBase
6 | {
7 | [Inject] protected ISnackbar Snackbar { get; set; } = default!;
8 | [Inject] protected IDialogService DialogService { get; set; } = default!;
9 | [Inject] protected NavigationManager Navigation { get; set; } = default!;
10 | }
11 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/Specifications/OrderExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 |
3 | namespace FSH.Framework.Persistence;
4 |
5 | ///
6 | /// Normalized representation of an ordering expression for specifications.
7 | ///
8 | /// The root entity type.
9 | public sealed record OrderExpression(
10 | Expression> KeySelector,
11 | bool Descending)
12 | where T : class;
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/IdentityModuleConstants.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Web.Modules;
2 |
3 | namespace FSH.Modules.Identity;
4 |
5 | public sealed class IdentityModuleConstants : IModuleConstants
6 | {
7 | public string ModuleId => "Identity";
8 |
9 | public string ModuleName => "Identity";
10 |
11 | public string ApiPrefix => "identity";
12 | public const string SchemaName = "identity";
13 | public const int PasswordLength = 10;
14 | }
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Inbox/IInboxStore.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing.Inbox;
2 |
3 | ///
4 | /// Abstraction for idempotent consumer tracking.
5 | ///
6 | public interface IInboxStore
7 | {
8 | Task HasProcessedAsync(Guid eventId, string handlerName, CancellationToken ct = default);
9 |
10 | Task MarkProcessedAsync(Guid eventId, string handlerName, string? tenantId, string eventType, CancellationToken ct = default);
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetAuditSummary/GetAuditSummaryQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Auditing.Contracts.v1.GetAuditSummary;
5 |
6 | public sealed class GetAuditSummaryQuery : IQuery
7 | {
8 | public DateTime? FromUtc { get; init; }
9 |
10 | public DateTime? ToUtc { get; init; }
11 |
12 | public string? TenantId { get; init; }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/ExceptionSeverityClassifier.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public static class ExceptionSeverityClassifier
4 | {
5 | public static AuditSeverity Classify(Exception ex) =>
6 | ex switch
7 | {
8 | OperationCanceledException => AuditSeverity.Information,
9 | UnauthorizedAccessException => AuditSeverity.Warning,
10 | _ => AuditSeverity.Error
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Tabs/FshTabs.razor:
--------------------------------------------------------------------------------
1 |
6 | @ChildContent
7 |
8 |
9 | @code {
10 | [Parameter] public RenderFragment? ChildContent { get; set; }
11 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/UpsertRole/UpsertRoleCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Roles.UpsertRole;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Roles.UpsertRole;
5 |
6 | public class UpsertRoleCommandValidator : AbstractValidator
7 | {
8 | public UpsertRoleCommandValidator()
9 | {
10 | RuleFor(x => x.Name).NotEmpty().WithMessage("Role name is required.");
11 | }
12 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/EntityChangeEventPayload.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public sealed record EntityChangeEventPayload(
4 | string DbContext,
5 | string? Schema,
6 | string Table,
7 | string EntityName,
8 | string Key, // unified string key (e.g., "Id:42" or "TenantId:1|UserId:42")
9 | EntityOperation Operation,
10 | IReadOnlyList Changes,
11 | string? TransactionId
12 | );
13 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetAuditsByTrace/GetAuditsByTraceQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Auditing.Contracts.v1.GetAuditsByTrace;
5 |
6 | public sealed class GetAuditsByTraceQuery : IQuery>
7 | {
8 | public string TraceId { get; init; } = default!;
9 |
10 | public DateTime? FromUtc { get; init; }
11 |
12 | public DateTime? ToUtc { get; init; }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Core
5 | FSH.Framework.Core
6 | FullStackHero.Framework.Core
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Infrastructure/Http/HttpContextRoutingExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Http;
2 | using Microsoft.AspNetCore.Routing;
3 |
4 | namespace FSH.Modules.Auditing;
5 |
6 | internal static class HttpContextRoutingExtensions
7 | {
8 | public static string? GetRoutePattern(this HttpContext ctx)
9 | => ctx.GetEndpoint() switch
10 | {
11 | RouteEndpoint re => re.RoutePattern.RawText,
12 | _ => null
13 | };
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/PasswordHistory/PasswordHistory.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Features.v1.Users.PasswordHistory;
2 |
3 | public class PasswordHistory
4 | {
5 | public int Id { get; set; }
6 | public string UserId { get; set; } = default!;
7 | public string PasswordHash { get; set; } = default!;
8 | public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
9 |
10 | // Navigation property
11 | public virtual FshUser? User { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/v1/GetTenants/GetTenantsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Persistence;
2 | using FSH.Modules.Multitenancy.Contracts.Dtos;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Multitenancy.Contracts.v1.GetTenants;
6 |
7 | public sealed class GetTenantsQuery : IPagedQuery, IQuery>
8 | {
9 | public int? PageNumber { get; set; }
10 |
11 | public int? PageSize { get; set; }
12 |
13 | public string? Sort { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Dtos/TenantDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.Dtos;
2 |
3 | public sealed class TenantDto
4 | {
5 | public string Id { get; set; } = default!;
6 | public string Name { get; set; } = default!;
7 | public string? ConnectionString { get; set; }
8 | public string AdminEmail { get; set; } = default!;
9 | public bool IsActive { get; set; }
10 | public DateTime ValidUpto { get; set; }
11 | public string? Issuer { get; set; }
12 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetAuditsByCorrelation/GetAuditsByCorrelationQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts.Dtos;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Auditing.Contracts.v1.GetAuditsByCorrelation;
5 |
6 | public sealed class GetAuditsByCorrelationQuery : IQuery>
7 | {
8 | public string CorrelationId { get; init; } = default!;
9 |
10 | public DateTime? FromUtc { get; init; }
11 |
12 | public DateTime? ToUtc { get; init; }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/FshRole.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 |
3 | namespace FSH.Modules.Identity.Features.v1.Roles;
4 |
5 | public class FshRole : IdentityRole
6 | {
7 | public string? Description { get; set; }
8 |
9 | public FshRole(string name, string? description = null)
10 | : base(name)
11 | {
12 | ArgumentNullException.ThrowIfNull(name);
13 |
14 | Description = description;
15 | NormalizedName = name.ToUpperInvariant();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Persistence/DbProviders.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Persistence;
2 |
3 | ///
4 | /// Supported database providers for the starter kit.
5 | ///
6 | public static class DbProviders
7 | {
8 | ///
9 | /// PostgreSQL database provider.
10 | ///
11 | public const string PostgreSQL = "POSTGRESQL";
12 |
13 | ///
14 | /// Microsoft SQL Server (MSSQL) database provider.
15 | ///
16 | public const string MSSQL = "MSSQL";
17 | }
18 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Observability/Logging/Serilog/StaticLogger.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using Serilog.Core;
3 |
4 | namespace FSH.Framework.Web.Observability.Logging.Serilog;
5 |
6 | public static class StaticLogger
7 | {
8 | public static void EnsureInitialized()
9 | {
10 | if (Log.Logger is not Logger)
11 | {
12 | Log.Logger = new LoggerConfiguration()
13 | .Enrich.FromLogContext()
14 | .WriteTo.Console()
15 | .CreateLogger();
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Abstractions/IEventBus.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing.Abstractions;
2 |
3 | ///
4 | /// Abstraction over an event bus. The initial provider is in-memory; additional providers
5 | /// can be added without changing modules that publish or handle events.
6 | ///
7 | public interface IEventBus
8 | {
9 | Task PublishAsync(IIntegrationEvent @event, CancellationToken ct = default);
10 |
11 | Task PublishAsync(IEnumerable events, CancellationToken ct = default);
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/ChangeTenantActivation/ChangeTenantActivationCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Multitenancy.Contracts.v1.ChangeTenantActivation;
3 |
4 | namespace FSH.Modules.Multitenancy.Features.v1.ChangeTenantActivation;
5 |
6 | internal sealed class ChangeTenantActivationCommandValidator : AbstractValidator
7 | {
8 | public ChangeTenantActivationCommandValidator() =>
9 | RuleFor(t => t.TenantId)
10 | .NotEmpty();
11 | }
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/Pages/Counter.razor:
--------------------------------------------------------------------------------
1 | @page "/counter"
2 |
3 | Counter
4 |
5 | Counter
6 |
7 | Current count: @currentCount
8 |
9 | Click me
10 |
11 | @code {
12 | private int currentCount = 0;
13 |
14 | private void IncrementCount()
15 | {
16 | currentCount++;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Cards/FshCard.razor:
--------------------------------------------------------------------------------
1 |
2 | @ChildContent
3 |
4 |
5 | @code {
6 | [Parameter] public string? Class { get; set; }
7 | [Parameter] public RenderFragment? ChildContent { get; set; }
8 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
9 |
10 | private string CssClass => string.IsNullOrWhiteSpace(Class) ? "fsh-card" : $"fsh-card {Class}";
11 | }
12 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Dtos/TenantStatusDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.Dtos;
2 |
3 | public sealed class TenantStatusDto
4 | {
5 | public string Id { get; init; } = default!;
6 | public string Name { get; init; } = default!;
7 | public bool IsActive { get; init; }
8 | public DateTime ValidUpto { get; init; }
9 | public bool HasConnectionString { get; init; }
10 | public string AdminEmail { get; init; } = default!;
11 | public string? Issuer { get; init; }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ForgotPassword/ForgotPasswordCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Users.ForgotPassword;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Users.ForgotPassword;
5 |
6 | public class ForgotPasswordCommandValidator : AbstractValidator
7 | {
8 | public ForgotPasswordCommandValidator()
9 | {
10 | RuleFor(p => p.Email).Cascade(CascadeMode.Stop)
11 | .NotEmpty()
12 | .EmailAddress();
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/UpgradeTenant/UpgradeTenantCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Multitenancy.Contracts.v1.UpgradeTenant;
3 |
4 | namespace FSH.Modules.Multitenancy.Features.v1.UpgradeTenant;
5 |
6 | public sealed class UpgradeTenantCommandValidator : AbstractValidator
7 | {
8 | public UpgradeTenantCommandValidator()
9 | {
10 | RuleFor(t => t.Tenant).NotEmpty();
11 | RuleFor(t => t.ExtendedExpiryDate).GreaterThan(DateTime.UtcNow);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Persistence/PagedResponse.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Persistence;
2 |
3 | public sealed class PagedResponse
4 | {
5 | public IReadOnlyCollection Items { get; init; } = Array.Empty();
6 |
7 | public int PageNumber { get; init; }
8 |
9 | public int PageSize { get; init; }
10 |
11 | public long TotalCount { get; init; }
12 |
13 | public int TotalPages { get; init; }
14 |
15 | public bool HasNext => PageNumber < TotalPages;
16 |
17 | public bool HasPrevious => PageNumber > 1;
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/ActivityEventPayload.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public sealed record ActivityEventPayload(
4 | ActivityKind Kind,
5 | string Name, // route template, command/query name, job id
6 | int? StatusCode,
7 | int DurationMs,
8 | BodyCapture Captured, // Request/Response/Both/None
9 | int RequestSize,
10 | int ResponseSize,
11 | object? RequestPreview, // truncated/filtered snapshot (JSON-friendly)
12 | object? ResponsePreview
13 | );
14 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Display/FshBadge.razor:
--------------------------------------------------------------------------------
1 |
2 | @ChildContent
3 |
4 |
5 | @code {
6 | [Parameter] public Color Color { get; set; } = Color.Primary;
7 | [Parameter] public Variant Variant { get; set; } = Variant.Filled;
8 | [Parameter] public RenderFragment? ChildContent { get; set; }
9 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/Services/ITokenService.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using System.Security.Claims;
3 |
4 | namespace FSH.Modules.Identity.Contracts.Services;
5 |
6 | public interface ITokenService
7 | {
8 | ///
9 | /// Issues a new access and refresh token for the specified subject.
10 | ///
11 | Task IssueAsync(
12 | string subject,
13 | IEnumerable claims,
14 | string? tenant = null,
15 | CancellationToken ct = default);
16 | }
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Data/Configurations/AppTenantInfoConfiguration.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Multitenancy;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | namespace FSH.Modules.Multitenancy.Data.Configurations;
6 |
7 | public class AppTenantInfoConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | builder.ToTable("Tenants", MultitenancyConstants.Schema);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/Routes.razor:
--------------------------------------------------------------------------------
1 | @using FSH.Playground.Blazor.Components.Layout
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Sorry, there's nothing at this address.
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Roles/UpdatePermissions/UpdatePermissionsCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Roles.UpdatePermissions;
4 |
5 | public class UpdatePermissionsCommand : ICommand
6 | {
7 | ///
8 | /// The ID of the role to update.
9 | ///
10 | public string RoleId { get; init; } = default!;
11 |
12 | ///
13 | /// The list of permissions to assign to the role.
14 | ///
15 | public List Permissions { get; init; } = [];
16 | }
17 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ResetPassword/ResetPasswordCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Users.ResetPassword;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Users.ResetPassword;
5 |
6 | public class ResetPasswordCommandValidator : AbstractValidator
7 | {
8 | public ResetPasswordCommandValidator()
9 | {
10 | RuleFor(x => x.Email).NotEmpty().EmailAddress();
11 | RuleFor(x => x.Password).NotEmpty();
12 | RuleFor(x => x.Token).NotEmpty();
13 | }
14 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/UpdateRolePermissions/UpdatePermissionsCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Roles.UpdatePermissions;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Roles.UpdateRolePermissions;
5 |
6 | public class UpdatePermissionsCommandValidator : AbstractValidator
7 | {
8 | public UpdatePermissionsCommandValidator()
9 | {
10 | RuleFor(r => r.RoleId)
11 | .NotEmpty();
12 | RuleFor(r => r.Permissions)
13 | .NotNull();
14 | }
15 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/ResourceConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Constants;
2 | public static class ResourceConstants
3 | {
4 | public const string Tenants = nameof(Tenants);
5 | public const string Dashboard = nameof(Dashboard);
6 | public const string Hangfire = nameof(Hangfire);
7 | public const string Users = nameof(Users);
8 | public const string UserRoles = nameof(UserRoles);
9 | public const string Roles = nameof(Roles);
10 | public const string RoleClaims = nameof(RoleClaims);
11 | public const string AuditTrails = nameof(AuditTrails);
12 | }
13 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/UserDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 | public class UserDto
3 | {
4 | public string? Id { get; set; }
5 |
6 | public string? UserName { get; set; }
7 |
8 | public string? FirstName { get; set; }
9 |
10 | public string? LastName { get; set; }
11 |
12 | public string? Email { get; set; }
13 |
14 | public bool IsActive { get; set; } = true;
15 |
16 | public bool EmailConfirmed { get; set; }
17 |
18 | public string? PhoneNumber { get; set; }
19 |
20 | public string? ImageUrl { get; set; }
21 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/DomainEvent.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | /// Base domain event with correlation and tenant context.
3 | public abstract record DomainEvent(
4 | Guid EventId,
5 | DateTimeOffset OccurredOnUtc,
6 | string? CorrelationId = null,
7 | string? TenantId = null
8 | ) : IDomainEvent
9 | {
10 | public static T Create(Func factory)
11 | where T : DomainEvent
12 | {
13 | ArgumentNullException.ThrowIfNull(factory);
14 | return factory(Guid.NewGuid(), DateTimeOffset.UtcNow);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/Events/UserRegisteredIntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Eventing.Abstractions;
2 |
3 | namespace FSH.Modules.Identity.Contracts.Events;
4 |
5 | ///
6 | /// Integration event raised when a new user is registered.
7 | ///
8 | public sealed record UserRegisteredIntegrationEvent(
9 | Guid Id,
10 | DateTime OccurredOnUtc,
11 | string? TenantId,
12 | string CorrelationId,
13 | string Source,
14 | string UserId,
15 | string Email,
16 | string FirstName,
17 | string LastName)
18 | : IIntegrationEvent;
19 |
20 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/Authorization/EndpointExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 |
3 | namespace FSH.Framework.Shared.Identity.Authorization;
4 |
5 | public static class EndpointExtensions
6 | {
7 | public static TBuilder RequirePermission(
8 | this TBuilder endpointConventionBuilder, string requiredPermission, params string[] additionalRequiredPermissions)
9 | where TBuilder : IEndpointConventionBuilder
10 | {
11 | return endpointConventionBuilder.WithMetadata(new RequiredPermissionAttribute(requiredPermission, additionalRequiredPermissions));
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/Modules.Auditing.Contracts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Modules.Auditing.Contracts
5 | FSH.Modules.Auditing.Contracts
6 | FullStackHero.Modules.Auditing.Contracts
7 | $(NoWarn);S2094
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/UpdateUser/UpdateUserCommand.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Storage.DTOs;
2 | using Mediator;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.UpdateUser;
5 |
6 | public class UpdateUserCommand : ICommand
7 | {
8 | public string Id { get; set; } = default!;
9 | public string? FirstName { get; set; }
10 | public string? LastName { get; set; }
11 | public string? PhoneNumber { get; set; }
12 | public string? Email { get; set; }
13 | public FileUploadRequest? Image { get; set; }
14 | public bool DeleteCurrentImage { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Modules.Auditing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Modules.Auditing
5 | FSH.Modules.Auditing
6 | FullStackHero.Modules.Auditing
7 | $(NoWarn);CA1031;CA1812;CA1859;S3267
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Caching/ICacheService.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Caching;
2 | public interface ICacheService
3 | {
4 | Task GetItemAsync(string key, CancellationToken ct = default);
5 | Task SetItemAsync(string key, T value, TimeSpan? sliding = default, CancellationToken ct = default);
6 | Task RemoveItemAsync(string key, CancellationToken ct = default);
7 | Task RefreshItemAsync(string key, CancellationToken ct = default);
8 | T? GetItem(string key);
9 | void SetItem(string key, T value, TimeSpan? sliding = default);
10 | void RemoveItem(string key);
11 | void RefreshItem(string key);
12 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditSummary/GetAuditSummaryQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetAuditSummary;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetAuditSummary;
5 |
6 | public sealed class GetAuditSummaryQueryValidator : AbstractValidator
7 | {
8 | public GetAuditSummaryQueryValidator()
9 | {
10 | RuleFor(q => q)
11 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
12 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Domain/BaseEntity.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Core.Domain;
2 | public abstract class BaseEntity : IEntity, IHasDomainEvents
3 | {
4 | private readonly List _domainEvents = [];
5 |
6 | public TId Id { get; protected set; } = default!;
7 |
8 | public IReadOnlyCollection DomainEvents => _domainEvents;
9 |
10 | /// Raise and record a domain event for later dispatch.
11 | protected void AddDomainEvent(IDomainEvent @event)
12 | => _domainEvents.Add(@event);
13 |
14 | public void ClearDomainEvents() => _domainEvents.Clear();
15 | }
16 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/IAuditPublisher.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | ///
4 | /// Low-latency, non-blocking publisher. Implement with a bounded channel + background worker.
5 | ///
6 | public interface IAuditPublisher
7 | {
8 | /// Publish an audit event. Implementations should avoid blocking the request path.
9 | ValueTask PublishAsync(IAuditEvent auditEvent, CancellationToken ct = default);
10 |
11 | /// Ambient scope for the current operation (usually request-scoped).
12 | IAuditScope CurrentScope { get; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/Services/IRoleService.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | namespace FSH.Modules.Identity.Contracts.Services;
4 |
5 | public interface IRoleService
6 | {
7 | Task> GetRolesAsync();
8 | Task GetRoleAsync(string id);
9 | Task CreateOrUpdateRoleAsync(string roleId, string name, string description);
10 | Task DeleteRoleAsync(string id);
11 | Task GetWithPermissionsAsync(string id, CancellationToken cancellationToken);
12 |
13 | Task UpdatePermissionsAsync(string roleId, List permissions);
14 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/ActionConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Constants;
2 | public static class ActionConstants
3 | {
4 | public const string View = nameof(View);
5 | public const string Search = nameof(Search);
6 | public const string Create = nameof(Create);
7 | public const string Update = nameof(Update);
8 | public const string Delete = nameof(Delete);
9 | public const string Export = nameof(Export);
10 | public const string Generate = nameof(Generate);
11 | public const string Clean = nameof(Clean);
12 | public const string UpgradeSubscription = nameof(UpgradeSubscription);
13 | }
14 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetSecurityAudits/GetSecurityAuditsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 | using FSH.Modules.Auditing.Contracts.Dtos;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Auditing.Contracts.v1.GetSecurityAudits;
6 |
7 | public sealed class GetSecurityAuditsQuery : IQuery>
8 | {
9 | public SecurityAction? Action { get; init; }
10 |
11 | public string? UserId { get; init; }
12 |
13 | public string? TenantId { get; init; }
14 |
15 | public DateTime? FromUtc { get; init; }
16 |
17 | public DateTime? ToUtc { get; init; }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Finbuckle.MultiTenant;
2 | using Finbuckle.MultiTenant.Abstractions;
3 | using Finbuckle.MultiTenant.AspNetCore.Extensions;
4 | using FSH.Modules.Multitenancy.Data;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | namespace FSH.Modules.Multitenancy;
9 |
10 | public static class Extensions
11 | {
12 | public static WebApplication UseHeroMultiTenantDatabases(this WebApplication app)
13 | {
14 | ArgumentNullException.ThrowIfNull(app);
15 | app.UseMultiTenant();
16 |
17 | return app;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetSecurityAudits/GetSecurityAuditsQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetSecurityAudits;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetSecurityAudits;
5 |
6 | public sealed class GetSecurityAuditsQueryValidator : AbstractValidator
7 | {
8 | public GetSecurityAuditsQueryValidator()
9 | {
10 | RuleFor(q => q)
11 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
12 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/ChangePassword/ChangePasswordCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 |
3 | namespace FSH.Modules.Identity.Contracts.v1.Users.ChangePassword;
4 |
5 | public class ChangePasswordCommand : ICommand
6 | {
7 | /// The user's current password.
8 | public string Password { get; init; } = default!;
9 |
10 | /// The new password the user wants to set.
11 | public string NewPassword { get; init; } = default!;
12 |
13 | /// Confirmation of the new password.
14 | public string ConfirmNewPassword { get; init; } = default!;
15 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetExceptionAudits/GetExceptionAuditsQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetExceptionAudits;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetExceptionAudits;
5 |
6 | public sealed class GetExceptionAuditsQueryValidator : AbstractValidator
7 | {
8 | public GetExceptionAuditsQueryValidator()
9 | {
10 | RuleFor(q => q)
11 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
12 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Tokens/RefreshToken/RefreshTokenCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Tokens.RefreshToken;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Tokens.RefreshToken;
5 |
6 | public class RefreshTokenCommandValidator : AbstractValidator
7 | {
8 | public RefreshTokenCommandValidator()
9 | {
10 | RuleFor(p => p.Token)
11 | .Cascade(CascadeMode.Stop)
12 | .NotEmpty();
13 |
14 | RuleFor(p => p.RefreshToken)
15 | .Cascade(CascadeMode.Stop)
16 | .NotEmpty();
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Data/Configurations/TenantProvisioningStepConfiguration.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Multitenancy;
2 | using FSH.Modules.Multitenancy.Provisioning;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
5 |
6 | namespace FSH.Modules.Multitenancy.Data.Configurations;
7 |
8 | public class TenantProvisioningStepConfiguration : IEntityTypeConfiguration
9 | {
10 | public void Configure(EntityTypeBuilder builder)
11 | {
12 | builder.ToTable("TenantProvisioningSteps", MultitenancyConstants.Schema);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using FSH.Playground.Blazor.Components.Layout
4 | @using Microsoft.AspNetCore.Authorization
5 | @using Microsoft.AspNetCore.Components.Forms
6 | @using Microsoft.AspNetCore.Components.Routing
7 | @using Microsoft.AspNetCore.Components.Web
8 | @using static Microsoft.AspNetCore.Components.Web.RenderMode
9 | @using Microsoft.AspNetCore.Components.Web.Virtualization
10 | @using Microsoft.JSInterop
11 | @using MudBlazor
12 | @using MudBlazor.Services
13 | @using FSH.Framework.Blazor.UI.Components.Page
14 | @using FSH.Framework.Blazor.UI.Components.User
15 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Mediator/Extensions.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Web.Mediator.Behaviors;
2 | using Mediator;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using System.Reflection;
5 |
6 | namespace FSH.Framework.Web.Mediator;
7 |
8 | public static class Extensions
9 | {
10 | public static IServiceCollection
11 | EnableMediator(this IServiceCollection services, params Assembly[] featureAssemblies)
12 | {
13 | ArgumentNullException.ThrowIfNull(services);
14 |
15 | // Behaviors
16 | services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
17 |
18 | return services;
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Dtos/TenantProvisioningStatusDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.Dtos;
2 |
3 | public sealed record TenantProvisioningStepDto(
4 | string Step,
5 | string Status,
6 | DateTime? StartedUtc,
7 | DateTime? CompletedUtc,
8 | string? Error);
9 |
10 | public sealed record TenantProvisioningStatusDto(
11 | string TenantId,
12 | string Status,
13 | string CorrelationId,
14 | string? CurrentStep,
15 | string? Error,
16 | DateTime CreatedUtc,
17 | DateTime? StartedUtc,
18 | DateTime? CompletedUtc,
19 | IReadOnlyCollection Steps);
20 |
--------------------------------------------------------------------------------
/scripts/openapi/check-openapi-drift.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env pwsh
2 | param(
3 | [Parameter(Mandatory = $false)]
4 | [string] $SpecUrl = "https://localhost:7030/openapi/v1.json"
5 | )
6 |
7 | Set-StrictMode -Version Latest
8 | $ErrorActionPreference = "Stop"
9 |
10 | $repoRoot = Resolve-Path "$PSScriptRoot/../.."
11 | Set-Location $repoRoot
12 |
13 | Write-Host "Running NSwag generation against $SpecUrl..."
14 | ./scripts/openapi/generate-api-clients.ps1 -SpecUrl $SpecUrl
15 |
16 | $targetFile = "src/Playground/Playground.Blazor/ApiClient/Generated.cs"
17 |
18 | Write-Host "Checking for drift in $targetFile..."
19 | git diff --exit-code -- $targetFile
20 |
21 | Write-Host "No drift detected."
22 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Feedback/FshAlert.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
8 | @ChildContent
9 |
10 |
11 | @code {
12 | [Parameter] public Severity Severity { get; set; } = Severity.Info;
13 | [Parameter] public Variant Variant { get; set; } = Variant.Filled;
14 | [Parameter] public RenderFragment? ChildContent { get; set; }
15 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Tokens/TokenGeneration/GenerateTokenCommandValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Tokens.TokenGeneration;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Tokens.TokenGeneration;
5 |
6 | public class TokenGenerationCommandValidator : AbstractValidator
7 | {
8 | public TokenGenerationCommandValidator()
9 | {
10 | RuleFor(p => p.Email)
11 | .Cascade(CascadeMode.Stop)
12 | .NotEmpty()
13 | .EmailAddress();
14 |
15 | RuleFor(p => p.Password)
16 | .Cascade(CascadeMode.Stop)
17 | .NotEmpty();
18 | }
19 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Inputs/FshSwitch.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
8 |
9 | @code {
10 | [Parameter] public bool Checked { get; set; }
11 | [Parameter] public EventCallback CheckedChanged { get; set; }
12 | [Parameter] public bool Disabled { get; set; }
13 | [Parameter] public Color Color { get; set; } = Color.Primary;
14 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Avatars/FshAvatar.razor:
--------------------------------------------------------------------------------
1 |
6 |
7 | @code {
8 | [Parameter] public string? Src { get; set; }
9 | [Parameter] public string? Text { get; set; }
10 | [Parameter] public Size Size { get; set; } = Size.Medium;
11 | [Parameter] public string? Class { get; set; }
12 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
13 |
14 | private string CssClass => string.IsNullOrWhiteSpace(Class) ? "fsh-avatar" : $"fsh-avatar {Class}";
15 | }
16 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Outbox/IOutboxStore.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Eventing.Abstractions;
2 |
3 | namespace FSH.Framework.Eventing.Outbox;
4 |
5 | ///
6 | /// Abstraction for persisting and reading outbox messages.
7 | ///
8 | public interface IOutboxStore
9 | {
10 | Task AddAsync(IIntegrationEvent @event, CancellationToken ct = default);
11 |
12 | Task> GetPendingBatchAsync(int batchSize, CancellationToken ct = default);
13 |
14 | Task MarkAsProcessedAsync(OutboxMessage message, CancellationToken ct = default);
15 |
16 | Task MarkAsFailedAsync(OutboxMessage message, string error, bool isDead, CancellationToken ct = default);
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/ISecurityAudit.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public interface ISecurityAudit
4 | {
5 | ValueTask LoginSucceededAsync(string userId, string userName, string clientId, string ip, string userAgent, CancellationToken ct = default);
6 | ValueTask LoginFailedAsync(string subjectIdOrName, string clientId, string reason, string ip, CancellationToken ct = default);
7 | ValueTask TokenIssuedAsync(string userId, string userName, string clientId, string tokenFingerprint, DateTime expiresUtc, CancellationToken ct = default);
8 | ValueTask TokenRevokedAsync(string userId, string clientId, string reason, CancellationToken ct = default);
9 | }
10 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditsByTrace/GetAuditsByTraceQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetAuditsByTrace;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetAuditsByTrace;
5 |
6 | public sealed class GetAuditsByTraceQueryValidator : AbstractValidator
7 | {
8 | public GetAuditsByTraceQueryValidator()
9 | {
10 | RuleFor(q => q.TraceId)
11 | .NotEmpty();
12 |
13 | RuleFor(q => q)
14 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
15 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetExceptionAudits/GetExceptionAuditsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 | using FSH.Modules.Auditing.Contracts.Dtos;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Auditing.Contracts.v1.GetExceptionAudits;
6 |
7 | public sealed class GetExceptionAuditsQuery : IQuery>
8 | {
9 | public ExceptionArea? Area { get; init; }
10 |
11 | public AuditSeverity? Severity { get; init; }
12 |
13 | public string? ExceptionType { get; init; }
14 |
15 | public string? RouteOrLocation { get; init; }
16 |
17 | public DateTime? FromUtc { get; init; }
18 |
19 | public DateTime? ToUtc { get; init; }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/SearchUsers/SearchUsersQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Persistence;
2 | using FSH.Modules.Identity.Contracts.DTOs;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Contracts.v1.Users.SearchUsers;
6 |
7 | public sealed class SearchUsersQuery : IPagedQuery, IQuery>
8 | {
9 | public int? PageNumber { get; set; }
10 |
11 | public int? PageSize { get; set; }
12 |
13 | public string? Sort { get; set; }
14 |
15 | public string? Search { get; set; }
16 |
17 | public bool? IsActive { get; set; }
18 |
19 | public bool? EmailConfirmed { get; set; }
20 |
21 | public string? RoleId { get; set; }
22 | }
23 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Modules/FshModuleAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace FSH.Framework.Web.Modules;
4 |
5 | [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
6 | public sealed class FshModuleAttribute : Attribute
7 | {
8 | public Type ModuleType { get; }
9 |
10 | ///
11 | /// Optional ordering hint that allows hosts to control module startup sequencing.
12 | /// Lower numbers execute first.
13 | ///
14 | public int Order { get; }
15 |
16 | public FshModuleAttribute(Type moduleType, int order = 0)
17 | {
18 | ModuleType = moduleType ?? throw new ArgumentNullException(nameof(moduleType));
19 | Order = order;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/GetTenantTheme/GetTenantThemeQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts;
2 | using FSH.Modules.Multitenancy.Contracts.Dtos;
3 | using FSH.Modules.Multitenancy.Contracts.v1.GetTenantTheme;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Multitenancy.Features.v1.GetTenantTheme;
7 |
8 | public sealed class GetTenantThemeQueryHandler(ITenantThemeService themeService)
9 | : IQueryHandler
10 | {
11 | public async ValueTask Handle(GetTenantThemeQuery query, CancellationToken cancellationToken)
12 | {
13 | return await themeService.GetCurrentTenantThemeAsync(cancellationToken);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Shared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Shared
5 | FSH.Framework.Shared
6 | FullStackHero.Framework.Shared
7 | $(NoWarn);CA1716;CA1711;CA1019;CA1305
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Modules.Multitenancy.Contracts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | FSH.Modules.Multitenancy.Contracts
4 | FSH.Modules.Multitenancy.Contracts
5 | FullStackHero.Modules.Multitenancy.Contracts
6 | $(NoWarn);CA1056;S2094
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/Modules.Identity.Contracts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Modules.Identity.Contracts
5 | FSH.Modules.Identity.Contracts
6 | FullStackHero.Modules.Identity.Contracts
7 | $(NoWarn);CA1002;CA1056;CS1572;S2094
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy.Contracts/Dtos/TenantMigrationStatusDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy.Contracts.Dtos;
2 |
3 | public sealed class TenantMigrationStatusDto
4 | {
5 | public string TenantId { get; set; } = default!;
6 |
7 | public string Name { get; set; } = string.Empty;
8 |
9 | public bool IsActive { get; set; }
10 |
11 | public DateTime? ValidUpto { get; set; }
12 |
13 | public bool HasPendingMigrations { get; set; }
14 |
15 | public string? Provider { get; set; }
16 |
17 | public string? LastAppliedMigration { get; set; }
18 |
19 | public IReadOnlyCollection PendingMigrations { get; set; } = Array.Empty();
20 |
21 | public string? Error { get; set; }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditsByCorrelation/GetAuditsByCorrelationQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetAuditsByCorrelation;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetAuditsByCorrelation;
5 |
6 | public sealed class GetAuditsByCorrelationQueryValidator : AbstractValidator
7 | {
8 | public GetAuditsByCorrelationQueryValidator()
9 | {
10 | RuleFor(q => q.CorrelationId)
11 | .NotEmpty();
12 |
13 | RuleFor(q => q)
14 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
15 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Infrastructure/Serialization/SystemTextJsonAuditSerializer.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace FSH.Modules.Auditing;
6 |
7 | public sealed class SystemTextJsonAuditSerializer : IAuditSerializer
8 | {
9 | private static readonly JsonSerializerOptions Opts = new()
10 | {
11 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
12 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
13 | Converters = { new JsonStringEnumConverter() },
14 | WriteIndented = false
15 | };
16 |
17 | public string SerializePayload(object payload) => JsonSerializer.Serialize(payload, Opts);
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/v1/Users/RegisterUser/RegisterUserCommand.cs:
--------------------------------------------------------------------------------
1 | using Mediator;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace FSH.Modules.Identity.Contracts.v1.Users.RegisterUser;
5 |
6 | public class RegisterUserCommand : ICommand
7 | {
8 | public string FirstName { get; set; } = default!;
9 | public string LastName { get; set; } = default!;
10 | public string Email { get; set; } = default!;
11 | public string UserName { get; set; } = default!;
12 | public string Password { get; set; } = default!;
13 | public string ConfirmPassword { get; set; } = default!;
14 | public string? PhoneNumber { get; set; }
15 |
16 | [JsonIgnore]
17 | public string? Origin { get; set; }
18 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Caching/CachingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Caching;
2 |
3 | public sealed class CachingOptions
4 | {
5 | /// Redis connection string. If empty, falls back to in-memory.
6 | public string Redis { get; set; } = string.Empty;
7 |
8 | /// Default sliding expiration if caller doesn't specify.
9 | public TimeSpan? DefaultSlidingExpiration { get; set; } = TimeSpan.FromMinutes(5);
10 |
11 | /// Default absolute expiration (cap).
12 | public TimeSpan? DefaultAbsoluteExpiration { get; set; } = TimeSpan.FromMinutes(15);
13 |
14 | /// Optional prefix (env/tenant/app) applied to all keys.
15 | public string? KeyPrefix { get; set; } = "fsh_";
16 | }
17 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/OpenApi/OpenApiOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.OpenApi;
2 | public sealed class OpenApiOptions
3 | {
4 | public required string Title { get; init; }
5 | public string Version { get; init; } = "v1";
6 | public required string Description { get; init; }
7 |
8 | public ContactOptions? Contact { get; init; }
9 | public LicenseOptions? License { get; init; }
10 |
11 | public sealed class ContactOptions
12 | {
13 | public string? Name { get; init; }
14 | public Uri? Url { get; init; }
15 | public string? Email { get; init; }
16 | }
17 |
18 | public sealed class LicenseOptions
19 | {
20 | public string? Name { get; init; }
21 | public Uri? Url { get; init; }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Data/PasswordPolicyOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Data;
2 |
3 | public class PasswordPolicyOptions
4 | {
5 | /// Number of previous passwords to keep in history (prevent reuse)
6 | public int PasswordHistoryCount { get; set; } = 5;
7 |
8 | /// Number of days before password expires and must be changed
9 | public int PasswordExpiryDays { get; set; } = 90;
10 |
11 | /// Number of days before expiry to show warning to user
12 | public int PasswordExpiryWarningDays { get; set; } = 14;
13 |
14 | /// Set to false to disable password expiry enforcement
15 | public bool EnforcePasswordExpiry { get; set; } = true;
16 | }
17 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Identity/RoleConstants.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 |
3 | namespace FSH.Framework.Shared.Constants;
4 |
5 | public static class RoleConstants
6 | {
7 | public const string Admin = nameof(Admin);
8 | public const string Basic = nameof(Basic);
9 |
10 | ///
11 | /// The base roles provided by the framework.
12 | ///
13 | public static IReadOnlyList DefaultRoles { get; } = new ReadOnlyCollection(new[]
14 | {
15 | Admin,
16 | Basic
17 | });
18 |
19 | ///
20 | /// Determines whether the role is a framework-defined default.
21 | ///
22 | public static bool IsDefault(string roleName) => DefaultRoles.Contains(roleName);
23 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Storage/Storage.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Storage
5 | FSH.Framework.Storage
6 | FullStackHero.Framework.Storage
7 | $(NoWarn);CA1031;CA1056;CA1002;CA2227;CA1812;CA1308;CA1062
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/AssignUserRoles/AssignUserRolesCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.AssignUserRoles;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.AssignUserRoles;
6 |
7 | public sealed class AssignUserRolesCommandHandler(IUserService userService)
8 | : ICommandHandler
9 | {
10 | public async ValueTask Handle(AssignUserRolesCommand command, CancellationToken cancellationToken)
11 | {
12 | ArgumentNullException.ThrowIfNull(command);
13 |
14 | return await userService.AssignRolesAsync(command.UserId, command.UserRoles, cancellationToken);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Button/FshButton.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
3 |
10 | @ChildContent
11 |
12 |
13 | @code {
14 | [Parameter] public Color Color { get; set; } = Color.Primary;
15 | [Parameter] public Variant Variant { get; set; } = Variant.Filled;
16 | [Parameter] public string? StartIcon { get; set; }
17 | [Parameter] public bool Disabled { get; set; }
18 | [Parameter] public EventCallback OnClick { get; set; }
19 | [Parameter] public RenderFragment? ChildContent { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Mailing/Mailing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Mailing
5 | FSH.Framework.Mailing
6 | FullStackHero.Framework.Mailing
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/GetTenantStatus/GetTenantStatusQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts;
2 | using FSH.Modules.Multitenancy.Contracts.Dtos;
3 | using FSH.Modules.Multitenancy.Contracts.v1.GetTenantStatus;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Multitenancy.Features.v1.GetTenantStatus;
7 |
8 | public sealed class GetTenantStatusQueryHandler(ITenantService tenantService)
9 | : IQueryHandler
10 | {
11 | public async ValueTask Handle(GetTenantStatusQuery query, CancellationToken cancellationToken)
12 | {
13 | ArgumentNullException.ThrowIfNull(query);
14 | return await tenantService.GetStatusAsync(query.TenantId);
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/src/Playground/FSH.Playground.AppHost/FSH.Playground.AppHost.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | enable
6 | enable
7 | 9fe5df9a-b9b2-4202-bdb4-d30b01b71d1a
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": false,
8 | "applicationUrl": "http://localhost:5030",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development"
11 | }
12 | },
13 | "https": {
14 | "commandName": "Project",
15 | "dotnetRunMessages": true,
16 | "launchBrowser": true,
17 | "launchUrl": "scalar",
18 | "applicationUrl": "https://localhost:7030;http://localhost:5030",
19 | "environmentVariables": {
20 | "ASPNETCORE_ENVIRONMENT": "Development"
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/Events/TokenGeneratedIntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Eventing.Abstractions;
2 |
3 | namespace FSH.Modules.Identity.Contracts.Events;
4 |
5 | ///
6 | /// Integration event raised when a JWT token is generated for a user.
7 | /// Intended primarily as a sample event to exercise the eventing/outbox pipeline.
8 | ///
9 | public sealed record TokenGeneratedIntegrationEvent(
10 | Guid Id,
11 | DateTime OccurredOnUtc,
12 | string? TenantId,
13 | string CorrelationId,
14 | string Source,
15 | string UserId,
16 | string Email,
17 | string ClientId,
18 | string IpAddress,
19 | string UserAgent,
20 | string TokenFingerprint,
21 | DateTime AccessTokenExpiresAtUtc)
22 | : IIntegrationEvent;
23 |
24 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "profiles": {
4 | "http": {
5 | "commandName": "Project",
6 | "dotnetRunMessages": true,
7 | "launchBrowser": true,
8 | "applicationUrl": "http://localhost:5032",
9 | "environmentVariables": {
10 | "ASPNETCORE_ENVIRONMENT": "Development"
11 | }
12 | },
13 | "https": {
14 | "commandName": "Project",
15 | "dotnetRunMessages": true,
16 | "launchBrowser": true,
17 | "applicationUrl": "https://localhost:7140;http://localhost:5032",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Auditing/AuditAttributes.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Auditing;
2 |
3 | /// Marks a property that should be excluded from audit diffs and payloads.
4 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
5 | public sealed class AuditIgnoreAttribute : Attribute { }
6 |
7 | ///
8 | /// Marks a property as sensitive (to be masked or hashed when serialized).
9 | ///
10 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
11 | public sealed class AuditSensitiveAttribute : Attribute
12 | {
13 | public bool Hash { get; init; }
14 | public bool Redact { get; init; }
15 |
16 | public AuditSensitiveAttribute(bool hash = false, bool redact = false)
17 | => (Hash, Redact) = (hash, redact);
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/Dtos/AuditSummaryAggregateDto.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 |
3 | namespace FSH.Modules.Auditing.Contracts.Dtos;
4 |
5 | public sealed class AuditSummaryAggregateDto
6 | {
7 | public IDictionary EventsByType { get; init; } =
8 | new Dictionary();
9 |
10 | public IDictionary EventsBySeverity { get; init; } =
11 | new Dictionary();
12 |
13 | public IDictionary EventsBySource { get; init; } =
14 | new Dictionary(StringComparer.OrdinalIgnoreCase);
15 |
16 | public IDictionary EventsByTenant { get; init; } =
17 | new Dictionary(StringComparer.OrdinalIgnoreCase);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Infrastructure/Http/ContentTypeHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Net.Http.Headers;
2 |
3 | namespace FSH.Modules.Auditing;
4 |
5 | internal static class ContentTypeHelper
6 | {
7 | public static bool IsJsonLike(string? contentType, ISet allowed)
8 | {
9 | if (string.IsNullOrWhiteSpace(contentType)) return false;
10 |
11 | // Prefer robust parse; fallback to naive split if needed.
12 | if (MediaTypeHeaderValue.TryParse(contentType, out var mt))
13 | return allowed.Contains(mt.MediaType.Value ?? string.Empty);
14 |
15 | var semi = contentType.IndexOf(';', StringComparison.Ordinal);
16 | var type = semi >= 0 ? contentType[..semi] : contentType;
17 | return allowed.Contains(type.Trim());
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Storage/FileType.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Storage;
2 |
3 | public enum FileType
4 | {
5 | Image,
6 | Document,
7 | Pdf
8 | }
9 |
10 | public class FileValidationRules
11 | {
12 | public IReadOnlyList AllowedExtensions { get; init; } = Array.Empty();
13 | public int MaxSizeInMB { get; init; } = 5;
14 | }
15 |
16 | public static class FileTypeMetadata
17 | {
18 | public static FileValidationRules GetRules(FileType type) =>
19 | type switch
20 | {
21 | FileType.Image => new() { AllowedExtensions = [".jpg", ".jpeg", ".png"], MaxSizeInMB = 5 },
22 | FileType.Pdf => new() { AllowedExtensions = [".pdf"], MaxSizeInMB = 10 },
23 | _ => throw new NotSupportedException($"Unsupported file type: {type}")
24 | };
25 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Persistence/IPagedQuery.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Persistence;
2 |
3 | ///
4 | /// Shared pagination and sorting contract that can be implemented
5 | /// or extended by module-specific request types.
6 | ///
7 | public interface IPagedQuery
8 | {
9 | ///
10 | /// 1-based page number. Values less than 1 are normalized to 1.
11 | ///
12 | int? PageNumber { get; set; }
13 |
14 | ///
15 | /// Requested page size. Implementations may enforce caps.
16 | ///
17 | int? PageSize { get; set; }
18 |
19 | ///
20 | /// Multi-column sort expression, for example: "Name,-CreatedOn".
21 | /// "-" prefix indicates descending order.
22 | ///
23 | string? Sort { get; set; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Inputs/FshCheckbox.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
10 |
11 | @code {
12 | [Parameter] public bool Checked { get; set; }
13 | [Parameter] public EventCallback CheckedChanged { get; set; }
14 | [Parameter] public string? Label { get; set; }
15 | [Parameter] public bool Disabled { get; set; }
16 | [Parameter] public Color Color { get; set; } = Color.Primary;
17 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/GetTenants/GetTenantsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Persistence;
2 | using FSH.Modules.Multitenancy.Contracts.Dtos;
3 | using FSH.Modules.Multitenancy.Contracts.v1.GetTenants;
4 | using FSH.Modules.Multitenancy.Contracts;
5 | using Mediator;
6 |
7 | namespace FSH.Modules.Multitenancy.Features.v1.GetTenants;
8 |
9 | public sealed class GetTenantsQueryHandler(ITenantService tenantService)
10 | : IQueryHandler>
11 | {
12 | public async ValueTask> Handle(GetTenantsQuery query, CancellationToken cancellationToken)
13 | {
14 | ArgumentNullException.ThrowIfNull(query);
15 | return await tenantService.GetAllAsync(query, cancellationToken).ConfigureAwait(false);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoles/GetRolesQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRoles;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoles;
7 |
8 | public sealed class GetRolesQueryHandler : IQueryHandler>
9 | {
10 | private readonly IRoleService _roleService;
11 |
12 | public GetRolesQueryHandler(IRoleService roleService)
13 | {
14 | _roleService = roleService;
15 | }
16 |
17 | public async ValueTask> Handle(GetRolesQuery query, CancellationToken cancellationToken)
18 | {
19 | return await _roleService.GetRolesAsync().ConfigureAwait(false);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUsers/GetUsersQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUsers;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Users.GetUsers;
7 |
8 | public sealed class GetUsersQueryHandler : IQueryHandler>
9 | {
10 | private readonly IUserService _userService;
11 |
12 | public GetUsersQueryHandler(IUserService userService)
13 | {
14 | _userService = userService;
15 | }
16 |
17 | public async ValueTask> Handle(GetUsersQuery query, CancellationToken cancellationToken)
18 | {
19 | return await _userService.GetListAsync(cancellationToken).ConfigureAwait(false);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Abstractions/IIntegrationEvent.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing.Abstractions;
2 |
3 | ///
4 | /// Base integration event contract used for cross-module and cross-service messaging.
5 | ///
6 | public interface IIntegrationEvent
7 | {
8 | Guid Id { get; }
9 |
10 | DateTime OccurredOnUtc { get; }
11 |
12 | ///
13 | /// Tenant identifier for tenant-scoped events. Null for global events.
14 | ///
15 | string? TenantId { get; }
16 |
17 | ///
18 | /// Correlation identifier to tie events to requests and traces.
19 | ///
20 | string CorrelationId { get; }
21 |
22 | ///
23 | /// Logical source of the event (e.g., module or service name).
24 | ///
25 | string Source { get; }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/Dtos/AuditSummaryDto.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 |
3 | namespace FSH.Modules.Auditing.Contracts.Dtos;
4 |
5 | public sealed class AuditSummaryDto
6 | {
7 | public Guid Id { get; set; }
8 |
9 | public DateTime OccurredAtUtc { get; set; }
10 |
11 | public AuditEventType EventType { get; set; }
12 |
13 | public AuditSeverity Severity { get; set; }
14 |
15 | public string? TenantId { get; set; }
16 |
17 | public string? UserId { get; set; }
18 |
19 | public string? UserName { get; set; }
20 |
21 | public string? TraceId { get; set; }
22 |
23 | public string? CorrelationId { get; set; }
24 |
25 | public string? RequestId { get; set; }
26 |
27 | public string? Source { get; set; }
28 |
29 | public AuditTag Tags { get; set; }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAudits/GetAuditsQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Auditing.Contracts.v1.GetAudits;
3 |
4 | namespace FSH.Modules.Auditing.Features.v1.GetAudits;
5 |
6 | public sealed class GetAuditsQueryValidator : AbstractValidator
7 | {
8 | public GetAuditsQueryValidator()
9 | {
10 | RuleFor(q => q.PageNumber)
11 | .GreaterThan(0)
12 | .When(q => q.PageNumber.HasValue);
13 |
14 | RuleFor(q => q.PageSize)
15 | .InclusiveBetween(1, 100)
16 | .When(q => q.PageSize.HasValue);
17 |
18 | RuleFor(q => q)
19 | .Must(q => !q.FromUtc.HasValue || !q.ToUtc.HasValue || q.FromUtc <= q.ToUtc)
20 | .WithMessage("FromUtc must be less than or equal to ToUtc.");
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Data/Configurations/TenantProvisioningConfiguration.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Multitenancy;
2 | using FSH.Modules.Multitenancy.Provisioning;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
5 |
6 | namespace FSH.Modules.Multitenancy.Data.Configurations;
7 |
8 | public class TenantProvisioningConfiguration : IEntityTypeConfiguration
9 | {
10 | public void Configure(EntityTypeBuilder builder)
11 | {
12 | ArgumentNullException.ThrowIfNull(builder);
13 |
14 | builder.ToTable("TenantProvisionings", MultitenancyConstants.Schema);
15 |
16 | builder.HasMany(p => p.Steps)
17 | .WithOne(s => s.Provisioning!)
18 | .HasForeignKey(s => s.ProvisioningId);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/UpgradeTenant/UpgradeTenantCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts;
2 | using FSH.Modules.Multitenancy.Contracts.v1.UpgradeTenant;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Multitenancy.Features.v1.UpgradeTenant;
6 |
7 | public sealed class UpgradeTenantCommandHandler(ITenantService service)
8 | : ICommandHandler
9 | {
10 | public async ValueTask Handle(UpgradeTenantCommand command, CancellationToken cancellationToken)
11 | {
12 | ArgumentNullException.ThrowIfNull(command);
13 | var validUpto = await service.UpgradeSubscription(command.Tenant, command.ExtendedExpiryDate);
14 | return new UpgradeTenantCommandResponse(validUpto, command.Tenant);
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Tools/CLI/Program.cs:
--------------------------------------------------------------------------------
1 | using FSH.CLI.Commands;
2 | using Spectre.Console.Cli;
3 |
4 | var app = new CommandApp();
5 |
6 | app.Configure(config =>
7 | {
8 | config.SetApplicationName("fsh");
9 | config.SetApplicationVersion(GetVersion());
10 |
11 | config.AddCommand("new")
12 | .WithDescription("Create a new FullStackHero project")
13 | .WithExample("new")
14 | .WithExample("new", "MyApp")
15 | .WithExample("new", "MyApp", "--preset", "quickstart")
16 | .WithExample("new", "MyApp", "--type", "api-blazor", "--arch", "monolith", "--db", "postgres");
17 | });
18 |
19 | return await app.RunAsync(args);
20 |
21 | static string GetVersion()
22 | {
23 | var assembly = typeof(Program).Assembly;
24 | var version = assembly.GetName().Version;
25 | return version?.ToString(3) ?? "1.0.0";
26 | }
27 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Persistence/AuditRecord.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing;
2 |
3 | public sealed class AuditRecord
4 | {
5 | public Guid Id { get; set; }
6 | public DateTime OccurredAtUtc { get; set; }
7 | public DateTime ReceivedAtUtc { get; set; }
8 |
9 | public int EventType { get; set; }
10 | public byte Severity { get; set; }
11 |
12 | public string? TenantId { get; set; }
13 | public string? UserId { get; set; }
14 | public string? UserName { get; set; }
15 | public string? TraceId { get; set; }
16 | public string? SpanId { get; set; }
17 | public string? CorrelationId { get; set; }
18 | public string? RequestId { get; set; }
19 | public string? Source { get; set; }
20 |
21 | public long Tags { get; set; }
22 |
23 | public string PayloadJson { get; set; } = default!;
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity.Contracts/DTOs/UserSessionDto.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Identity.Contracts.DTOs;
2 |
3 | public class UserSessionDto
4 | {
5 | public Guid Id { get; set; }
6 | public string? UserId { get; set; }
7 | public string? UserName { get; set; }
8 | public string? UserEmail { get; set; }
9 | public string? IpAddress { get; set; }
10 | public string? DeviceType { get; set; }
11 | public string? Browser { get; set; }
12 | public string? BrowserVersion { get; set; }
13 | public string? OperatingSystem { get; set; }
14 | public string? OsVersion { get; set; }
15 | public DateTime CreatedAt { get; set; }
16 | public DateTime LastActivityAt { get; set; }
17 | public DateTime ExpiresAt { get; set; }
18 | public bool IsActive { get; set; }
19 | public bool IsCurrentSession { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Chips/FshChip.razor:
--------------------------------------------------------------------------------
1 |
8 | @ChildContent
9 |
10 |
11 | @code {
12 | [Parameter] public Color Color { get; set; } = Color.Default;
13 | [Parameter] public Variant Variant { get; set; } = Variant.Filled;
14 | [Parameter] public string? Icon { get; set; }
15 | [Parameter] public string? Class { get; set; }
16 | [Parameter] public RenderFragment? ChildContent { get; set; }
17 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
18 |
19 | private string CssClass => string.IsNullOrWhiteSpace(Class) ? "fsh-chip" : $"fsh-chip {Class}";
20 | }
21 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/AuditHttpOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Auditing.Contracts;
2 |
3 | public sealed class AuditHttpOptions
4 | {
5 | public bool CaptureBodies { get; set; } = true;
6 | public int MaxRequestBytes { get; set; } = 8_192;
7 | public int MaxResponseBytes { get; set; } = 16_384;
8 |
9 | public HashSet AllowedContentTypes { get; } =
10 | new(StringComparer.OrdinalIgnoreCase)
11 | {
12 | "application/json",
13 | "application/problem+json"
14 | };
15 |
16 | public HashSet ExcludePathStartsWith { get; } =
17 | new(StringComparer.OrdinalIgnoreCase)
18 | {
19 | "/health", "/metrics", "/_framework", "/swagger", "/scalar", "/openapi"
20 | };
21 |
22 | public AuditSeverity MinExceptionSeverity { get; set; } = AuditSeverity.Error;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoleById/GetRoleByIdQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRole;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoleById;
7 |
8 | public sealed class GetRoleByIdQueryHandler : IQueryHandler
9 | {
10 | private readonly IRoleService _roleService;
11 |
12 | public GetRoleByIdQueryHandler(IRoleService roleService)
13 | {
14 | _roleService = roleService;
15 | }
16 |
17 | public async ValueTask Handle(GetRoleQuery query, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(query);
20 | return await _roleService.GetRoleAsync(query.Id).ConfigureAwait(false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/DeleteUser/DeleteUserCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.DeleteUser;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.DeleteUser;
6 |
7 | public sealed class DeleteUserCommandHandler : ICommandHandler
8 | {
9 | private readonly IUserService _userService;
10 |
11 | public DeleteUserCommandHandler(IUserService userService)
12 | {
13 | _userService = userService;
14 | }
15 |
16 | public async ValueTask Handle(DeleteUserCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 |
20 | await _userService.DeleteAsync(command.Id).ConfigureAwait(false);
21 |
22 | return Unit.Value;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/DeleteRole/DeleteRoleCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Roles.DeleteRole;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Roles.DeleteRole;
6 |
7 | public sealed class DeleteRoleCommandHandler : ICommandHandler
8 | {
9 | private readonly IRoleService _roleService;
10 |
11 | public DeleteRoleCommandHandler(IRoleService roleService)
12 | {
13 | _roleService = roleService;
14 | }
15 |
16 | public async ValueTask Handle(DeleteRoleCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 |
20 | await _roleService.DeleteRoleAsync(command.Id).ConfigureAwait(false);
21 |
22 | return Unit.Value;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserById/GetUserByIdQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUser;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserById;
7 |
8 | public sealed class GetUserByIdQueryHandler : IQueryHandler
9 | {
10 | private readonly IUserService _userService;
11 |
12 | public GetUserByIdQueryHandler(IUserService userService)
13 | {
14 | _userService = userService;
15 | }
16 |
17 | public async ValueTask Handle(GetUserQuery query, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(query);
20 | return await _userService.GetAsync(query.Id, cancellationToken).ConfigureAwait(false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/Specifications/ISpecificationOfTResult.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 |
3 | namespace FSH.Framework.Persistence;
4 |
5 | ///
6 | /// Projected specification that composes a query for
7 | /// and then selects into .
8 | ///
9 | ///
10 | /// Includes may be ignored when a selector is present; behavior is documented
11 | /// at the evaluator/extension level.
12 | ///
13 | /// The root entity type.
14 | /// The projected result type.
15 | public interface ISpecification : ISpecification
16 | where T : class
17 | {
18 | ///
19 | /// Projection applied at the end of the query pipeline.
20 | ///
21 | Expression> Selector { get; }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/Playground/Migrations.PostgreSQL/Migrations.PostgreSQL.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Playground.Migrations.PostgreSQL
5 | FSH.Playground.Migrations.PostgreSQL
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Blazor.UI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Blazor.UI
5 | FSH.Framework.Blazor.UI
6 | FullStackHero.Framework.Blazor.UI
7 |
8 | $(NoWarn);MUD0002
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Feedback/Snackbar/FshSnackbar.razor.cs:
--------------------------------------------------------------------------------
1 | using MudBlazor;
2 |
3 | namespace FSH.Framework.Blazor.UI.Components.Feedback.Snackbar;
4 |
5 | ///
6 | /// Convenience wrapper for snackbar calls with consistent styling.
7 | ///
8 | public sealed class FshSnackbar
9 | {
10 | private readonly ISnackbar _snackbar;
11 |
12 | public FshSnackbar(ISnackbar snackbar)
13 | {
14 | _snackbar = snackbar;
15 | }
16 |
17 | public void Success(string message) => Add(message, Severity.Success);
18 | public void Info(string message) => Add(message, Severity.Info);
19 | public void Warning(string message) => Add(message, Severity.Warning);
20 | public void Error(string message) => Add(message, Severity.Error);
21 |
22 | private void Add(string message, Severity severity)
23 | {
24 | _snackbar.Add(message, severity);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Exceptions/NotFoundException.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace FSH.Framework.Core.Exceptions;
4 |
5 | ///
6 | /// Exception representing a 404 Not Found error.
7 | ///
8 | public class NotFoundException : CustomException
9 | {
10 | public NotFoundException()
11 | : base("Resource not found.", Array.Empty(), HttpStatusCode.NotFound)
12 | {
13 | }
14 |
15 | public NotFoundException(string message)
16 | : base(message, Array.Empty(), HttpStatusCode.NotFound)
17 | {
18 | }
19 |
20 | public NotFoundException(string message, IEnumerable errors)
21 | : base(message, errors.ToList(), HttpStatusCode.NotFound)
22 | {
23 | }
24 |
25 | public NotFoundException(string message, Exception innerException)
26 | : base(message, innerException, HttpStatusCode.NotFound)
27 | {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Exceptions/ForbiddenException.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace FSH.Framework.Core.Exceptions;
4 | ///
5 | /// Exception representing a 403 Forbidden error.
6 | ///
7 | public class ForbiddenException : CustomException
8 | {
9 | public ForbiddenException()
10 | : base("Unauthorized access.", Array.Empty(), HttpStatusCode.Forbidden)
11 | {
12 | }
13 |
14 | public ForbiddenException(string message)
15 | : base(message, Array.Empty(), HttpStatusCode.Forbidden)
16 | {
17 | }
18 |
19 | public ForbiddenException(string message, IEnumerable errors)
20 | : base(message, errors.ToList(), HttpStatusCode.Forbidden)
21 | {
22 | }
23 |
24 | public ForbiddenException(string message, Exception innerException)
25 | : base(message, innerException, HttpStatusCode.Forbidden)
26 | {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Data/Pagination/FshPagination.razor:
--------------------------------------------------------------------------------
1 | @inherits FshComponentBase
2 |
10 |
11 | @code {
12 | [Parameter] public int Page { get; set; } = 1;
13 | [Parameter] public int PageSize { get; set; } = 10;
14 | [Parameter] public int[] PageSizeOptions { get; set; } = [10, 20, 50];
15 | [Parameter] public EventCallback PageChanged { get; set; }
16 | [Parameter] public EventCallback PageSizeChanged { get; set; }
17 | }
18 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/EventingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Eventing;
2 |
3 | ///
4 | /// Configuration options for the eventing building block.
5 | ///
6 | public sealed class EventingOptions
7 | {
8 | ///
9 | /// Provider for the event bus implementation. Defaults to InMemory.
10 | ///
11 | public string Provider { get; set; } = "InMemory";
12 |
13 | ///
14 | /// Batch size for outbox dispatching.
15 | ///
16 | public int OutboxBatchSize { get; set; } = 100;
17 |
18 | ///
19 | /// Maximum number of retries before an outbox message is marked as dead.
20 | ///
21 | public int OutboxMaxRetries { get; set; } = 5;
22 |
23 | ///
24 | /// Whether inbox-based idempotent handling is enabled.
25 | ///
26 | public bool EnableInbox { get; set; } = true;
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/scripts/openapi/generate-api-clients.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | [string]$SpecUrl = "https://localhost:7030/openapi/v1.json"
3 | )
4 |
5 | $ErrorActionPreference = "Stop"
6 |
7 | $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
8 | $repoRoot = Resolve-Path (Join-Path $scriptDir ".." "..")
9 | $configPath = Join-Path $scriptDir "nswag-playground.json"
10 | $outputDir = Join-Path $repoRoot "src/Playground/Playground.Blazor/ApiClient"
11 |
12 | Write-Host "Ensuring dotnet local tools are restored..." -ForegroundColor Cyan
13 | dotnet tool restore | Out-Host
14 |
15 | Write-Host "Ensuring output directory exists: $outputDir" -ForegroundColor Cyan
16 | New-Item -ItemType Directory -Force -Path $outputDir | Out-Null
17 |
18 | Write-Host "Generating API client from spec: $SpecUrl" -ForegroundColor Cyan
19 | dotnet nswag run $configPath /variables:SpecUrl=$SpecUrl
20 |
21 | Write-Host "Done. Generated clients are in $outputDir" -ForegroundColor Green
22 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/Components/Data/FshTable.razor:
--------------------------------------------------------------------------------
1 | @typeparam TItem
2 |
10 |
11 | @HeaderContent
12 |
13 |
14 | @RowTemplate
15 |
16 |
17 |
18 | @code {
19 | [Parameter] public IEnumerable Items { get; set; } = Array.Empty();
20 | [Parameter] public bool Loading { get; set; }
21 | [Parameter] public RenderFragment? HeaderContent { get; set; }
22 | [Parameter] public RenderFragment RowTemplate { get; set; } = default!;
23 | [Parameter(CaptureUnmatchedValues = true)] public Dictionary? AdditionalAttributes { get; set; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/SearchUsers/SearchUsersQueryValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Modules.Identity.Contracts.v1.Users.SearchUsers;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Users.SearchUsers;
5 |
6 | public sealed class SearchUsersQueryValidator : AbstractValidator
7 | {
8 | public SearchUsersQueryValidator()
9 | {
10 | RuleFor(q => q.PageNumber)
11 | .GreaterThan(0)
12 | .When(q => q.PageNumber.HasValue);
13 |
14 | RuleFor(q => q.PageSize)
15 | .InclusiveBetween(1, 100)
16 | .When(q => q.PageSize.HasValue);
17 |
18 | RuleFor(q => q.Search)
19 | .MaximumLength(200)
20 | .When(q => !string.IsNullOrEmpty(q.Search));
21 |
22 | RuleFor(q => q.RoleId)
23 | .MaximumLength(450)
24 | .When(q => !string.IsNullOrEmpty(q.RoleId));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ConfirmEmail/ConfirmEmailCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.ConfirmEmail;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.ConfirmEmail;
6 |
7 | public sealed class ConfirmEmailCommandHandler : ICommandHandler
8 | {
9 | private readonly IUserService _userService;
10 |
11 | public ConfirmEmailCommandHandler(IUserService userService)
12 | {
13 | _userService = userService;
14 | }
15 |
16 | public async ValueTask Handle(ConfirmEmailCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 |
20 | return await _userService.ConfirmEmailAsync(command.UserId, command.Code, command.Tenant, cancellationToken)
21 | .ConfigureAwait(false);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserRoles/GetUserRolesQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUserRoles;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserRoles;
7 |
8 | public sealed class GetUserRolesQueryHandler : IQueryHandler>
9 | {
10 | private readonly IUserService _userService;
11 |
12 | public GetUserRolesQueryHandler(IUserService userService)
13 | {
14 | _userService = userService;
15 | }
16 |
17 | public async ValueTask> Handle(GetUserRolesQuery query, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(query);
20 | return await _userService.GetUserRolesAsync(query.UserId, cancellationToken).ConfigureAwait(false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/UpdateRolePermissions/UpdatePermissionsCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Roles.UpdatePermissions;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Roles.UpdateRolePermissions;
6 |
7 | public sealed class UpdatePermissionsCommandHandler : ICommandHandler
8 | {
9 | private readonly IRoleService _roleService;
10 |
11 | public UpdatePermissionsCommandHandler(IRoleService roleService)
12 | {
13 | _roleService = roleService;
14 | }
15 |
16 | public async ValueTask Handle(UpdatePermissionsCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 | return await _roleService.UpdatePermissionsAsync(command.RoleId, command.Permissions).ConfigureAwait(false);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Sessions/GetUserSessions/GetUserSessionsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Sessions.GetUserSessions;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Sessions.GetUserSessions;
7 |
8 | public sealed class GetUserSessionsQueryHandler : IQueryHandler>
9 | {
10 | private readonly ISessionService _sessionService;
11 |
12 | public GetUserSessionsQueryHandler(ISessionService sessionService)
13 | {
14 | _sessionService = sessionService;
15 | }
16 |
17 | public async ValueTask> Handle(GetUserSessionsQuery query, CancellationToken cancellationToken)
18 | {
19 | return await _sessionService.GetUserSessionsForAdminAsync(query.UserId.ToString(), cancellationToken);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/IdentityMetrics.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.Metrics;
2 |
3 | namespace FSH.Modules.Identity;
4 |
5 | public sealed class IdentityMetrics : IDisposable
6 | {
7 | public const string MeterName = "FSH.Modules.Identity";
8 | private readonly Counter _tokensGenerated;
9 | private readonly Meter _meter;
10 |
11 | public IdentityMetrics(IMeterFactory meterFactory)
12 | {
13 | _meter = meterFactory.Create(MeterName);
14 |
15 | _tokensGenerated = _meter.CreateCounter(
16 | name: "identity_tokens_generated",
17 | unit: "tokens",
18 | description: "Number of tokens generated");
19 | }
20 |
21 | public void TokenGenerated(string emailId)
22 | {
23 | _tokensGenerated.Add(1, new KeyValuePair("user.email", emailId));
24 | }
25 |
26 | public void Dispose()
27 | {
28 | _meter?.Dispose();
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserProfile/GetCurrentUserProfileQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUserProfile;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserProfile;
7 |
8 | public sealed class GetCurrentUserProfileQueryHandler : IQueryHandler
9 | {
10 | private readonly IUserService _userService;
11 |
12 | public GetCurrentUserProfileQueryHandler(IUserService userService)
13 | {
14 | _userService = userService;
15 | }
16 |
17 | public async ValueTask Handle(GetCurrentUserProfileQuery query, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(query);
20 | return await _userService.GetAsync(query.UserId, cancellationToken).ConfigureAwait(false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/TenantProvisioning/GetTenantProvisioningStatus/GetTenantProvisioningStatusQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Multitenancy.Contracts.Dtos;
2 | using FSH.Modules.Multitenancy.Contracts.v1.TenantProvisioning;
3 | using FSH.Modules.Multitenancy.Provisioning;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Multitenancy.Features.v1.TenantProvisioning.GetTenantProvisioningStatus;
7 |
8 | public sealed class GetTenantProvisioningStatusQueryHandler(ITenantProvisioningService provisioningService)
9 | : IQueryHandler
10 | {
11 | public async ValueTask Handle(GetTenantProvisioningStatusQuery query, CancellationToken cancellationToken)
12 | {
13 | ArgumentNullException.ThrowIfNull(query);
14 | return await provisioningService.GetStatusAsync(query.TenantId, cancellationToken).ConfigureAwait(false);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Blazor.UI/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | namespace FSH.Framework.Blazor.UI;
3 |
4 | public static class ServiceCollectionExtensions
5 | {
6 | public static IServiceCollection AddHeroUI(this IServiceCollection services)
7 | {
8 | services.AddMudServices(options =>
9 | {
10 | options.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
11 | options.SnackbarConfiguration.ShowCloseIcon = true;
12 | options.SnackbarConfiguration.SnackbarVariant = Variant.Filled;
13 | options.SnackbarConfiguration.MaxDisplayedSnackbars = 3;
14 | });
15 |
16 | services.AddMudPopoverService();
17 | services.AddScoped();
18 | services.AddSingleton(FSH.Framework.Blazor.UI.Theme.FshTheme.Build());
19 |
20 | return services;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Persistence/AuditDbInitializer.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Persistence;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.Logging;
4 |
5 | namespace FSH.Modules.Auditing.Persistence;
6 |
7 | internal sealed class AuditDbInitializer(
8 | ILogger logger,
9 | AuditDbContext context) : IDbInitializer
10 | {
11 | public async Task MigrateAsync(CancellationToken cancellationToken)
12 | {
13 | if ((await context.Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any())
14 | {
15 | await context.Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
16 | logger.LogInformation("[{Tenant}] applied database migrations for audit module", context.TenantInfo?.Identifier);
17 | }
18 | }
19 |
20 | public Task SeedAsync(CancellationToken cancellationToken)
21 | {
22 | return Task.CompletedTask;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Versioning/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Asp.Versioning;
2 | using Microsoft.Extensions.DependencyInjection;
3 |
4 | namespace FSH.Framework.Web.Versioning;
5 |
6 | public static class Extensions
7 | {
8 | public static IServiceCollection AddHeroVersioning(this IServiceCollection services)
9 | {
10 | services
11 | .AddApiVersioning(options =>
12 | {
13 | options.ReportApiVersions = true;
14 | options.DefaultApiVersion = new ApiVersion(1, 0);
15 | options.AssumeDefaultVersionWhenUnspecified = true;
16 | options.ApiVersionReader = new UrlSegmentApiVersionReader();
17 | })
18 | .AddApiExplorer(options =>
19 | {
20 | options.GroupNameFormat = "'v'VVV";
21 | options.SubstituteApiVersionInUrl = true;
22 | })
23 | .EnableApiVersionBinding();
24 | return services;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserPermissions/GetCurrentUserPermissionsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.GetUserPermissions;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserPermissions;
6 |
7 | public sealed class GetCurrentUserPermissionsQueryHandler : IQueryHandler?>
8 | {
9 | private readonly IUserService _userService;
10 |
11 | public GetCurrentUserPermissionsQueryHandler(IUserService userService)
12 | {
13 | _userService = userService;
14 | }
15 |
16 | public async ValueTask?> Handle(GetCurrentUserPermissionsQuery query, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(query);
19 | return await _userService.GetPermissionsAsync(query.UserId, cancellationToken).ConfigureAwait(false);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Core/Exceptions/UnauthorizedException.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace FSH.Framework.Core.Exceptions;
4 | ///
5 | /// Exception representing a 401 Unauthorized error (authentication failure).
6 | ///
7 | public class UnauthorizedException : CustomException
8 | {
9 | public UnauthorizedException()
10 | : base("Authentication failed.", Array.Empty(), HttpStatusCode.Unauthorized)
11 | {
12 | }
13 |
14 | public UnauthorizedException(string message)
15 | : base(message, Array.Empty(), HttpStatusCode.Unauthorized)
16 | {
17 | }
18 |
19 | public UnauthorizedException(string message, IEnumerable errors)
20 | : base(message, errors.ToList(), HttpStatusCode.Unauthorized)
21 | {
22 | }
23 |
24 | public UnauthorizedException(string message, Exception innerException)
25 | : base(message, innerException, HttpStatusCode.Unauthorized)
26 | {
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/UpsertRole/UpsertRoleCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.UpsertRole;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Roles.UpsertRole;
7 |
8 | public sealed class UpsertRoleCommandHandler : ICommandHandler
9 | {
10 | private readonly IRoleService _roleService;
11 |
12 | public UpsertRoleCommandHandler(IRoleService roleService)
13 | {
14 | _roleService = roleService;
15 | }
16 |
17 | public async ValueTask Handle(UpsertRoleCommand command, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(command);
20 |
21 | return await _roleService.CreateOrUpdateRoleAsync(command.Id, command.Name, command.Description ?? string.Empty)
22 | .ConfigureAwait(false);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ResetPassword/ResetPasswordCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.ResetPassword;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.ResetPassword;
6 |
7 | public sealed class ResetPasswordCommandHandler : ICommandHandler
8 | {
9 | private readonly IUserService _userService;
10 |
11 | public ResetPasswordCommandHandler(IUserService userService)
12 | {
13 | _userService = userService;
14 | }
15 |
16 | public async ValueTask Handle(ResetPasswordCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 |
20 | await _userService.ResetPasswordAsync(command.Email, command.Password, command.Token, cancellationToken).ConfigureAwait(false);
21 |
22 | return "Password has been reset.";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Jobs/Jobs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Jobs
5 | FSH.Framework.Jobs
6 | FullStackHero.Framework.Jobs
7 | $(NoWarn);CA1031;S3376;S3993
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoleWithPermissions/GetRoleWithPermissionsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.DTOs;
2 | using FSH.Modules.Identity.Contracts.Services;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRoleWithPermissions;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoleWithPermissions;
7 |
8 | public sealed class GetRoleWithPermissionsQueryHandler : IQueryHandler
9 | {
10 | private readonly IRoleService _roleService;
11 |
12 | public GetRoleWithPermissionsQueryHandler(IRoleService roleService)
13 | {
14 | _roleService = roleService;
15 | }
16 |
17 | public async ValueTask Handle(GetRoleWithPermissionsQuery query, CancellationToken cancellationToken)
18 | {
19 | ArgumentNullException.ThrowIfNull(query);
20 | return await _roleService.GetWithPermissionsAsync(query.Id, cancellationToken).ConfigureAwait(false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoles/GetRolesEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRoles;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoles;
10 |
11 | public static class GetRolesEndpoint
12 | {
13 | public static RouteHandlerBuilder MapGetRolesEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/roles", (IMediator mediator, CancellationToken cancellationToken) =>
16 | mediator.Send(new GetRolesQuery(), cancellationToken))
17 | .WithName("ListRoles")
18 | .WithSummary("List all roles")
19 | .RequirePermission(IdentityPermissionConstants.Roles.View)
20 | .WithDescription("Retrieve all roles available for the current tenant.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUsers/GetUsersListEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUsers;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Users.GetUsers;
10 |
11 | public static class GetUsersListEndpoint
12 | {
13 | internal static RouteHandlerBuilder MapGetUsersListEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/users", (CancellationToken cancellationToken, IMediator mediator) =>
16 | mediator.Send(new GetUsersQuery(), cancellationToken))
17 | .WithName("ListUsers")
18 | .WithSummary("List users")
19 | .RequirePermission(IdentityPermissionConstants.Users.View)
20 | .WithDescription("Retrieve a list of users for the current tenant.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/UserImageValidator.cs:
--------------------------------------------------------------------------------
1 | using FluentValidation;
2 | using FSH.Framework.Storage;
3 | using FSH.Framework.Storage.DTOs;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users;
6 |
7 | public class UserImageValidator : AbstractValidator
8 | {
9 | public UserImageValidator() : this(FileType.Image) { }
10 | public UserImageValidator(FileType fileType)
11 | {
12 | var rules = FileTypeMetadata.GetRules(fileType);
13 |
14 | RuleFor(x => x.FileName)
15 | .NotEmpty()
16 | .Must(file => rules.AllowedExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase)))
17 | .WithMessage($"Only these extensions are allowed: {string.Join(", ", rules.AllowedExtensions)}");
18 |
19 | RuleFor(x => x.Data)
20 | .NotEmpty()
21 | .Must(data => data.Count <= rules.MaxSizeInMB * 1024 * 1024)
22 | .WithMessage($"File must be <= {rules.MaxSizeInMB} MB.");
23 | }
24 | }
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/ResetTenantTheme/ResetTenantThemeCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using Finbuckle.MultiTenant.Abstractions;
2 | using FSH.Framework.Shared.Multitenancy;
3 | using FSH.Modules.Multitenancy.Contracts;
4 | using FSH.Modules.Multitenancy.Contracts.v1.ResetTenantTheme;
5 | using Mediator;
6 |
7 | namespace FSH.Modules.Multitenancy.Features.v1.ResetTenantTheme;
8 |
9 | public sealed class ResetTenantThemeCommandHandler(
10 | ITenantThemeService themeService,
11 | IMultiTenantContextAccessor tenantAccessor)
12 | : ICommandHandler
13 | {
14 | public async ValueTask Handle(ResetTenantThemeCommand command, CancellationToken cancellationToken)
15 | {
16 | var tenantId = tenantAccessor.MultiTenantContext?.TenantInfo?.Id
17 | ?? throw new InvalidOperationException("No tenant context available");
18 |
19 | await themeService.ResetThemeAsync(tenantId, cancellationToken);
20 |
21 | return Unit.Value;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Web/Security/SecurityHeadersOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Web.Security;
2 |
3 | public sealed class SecurityHeadersOptions
4 | {
5 | ///
6 | /// Enables or disables the security headers middleware entirely.
7 | ///
8 | public bool Enabled { get; set; } = true;
9 |
10 | ///
11 | /// Paths to bypass (e.g., OpenAPI/Scalar assets).
12 | ///
13 | public string[] ExcludedPaths { get; set; } = ["/scalar", "/openapi"];
14 |
15 | ///
16 | /// Whether to allow inline styles in CSP (default true for MudBlazor/Scalar compatibility).
17 | ///
18 | public bool AllowInlineStyles { get; set; } = true;
19 |
20 | ///
21 | /// Additional script sources to append to CSP.
22 | ///
23 | public string[] ScriptSources { get; set; } = [];
24 |
25 | ///
26 | /// Additional style sources to append to CSP.
27 | ///
28 | public string[] StyleSources { get; set; } = [];
29 | }
30 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyOptions.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Modules.Multitenancy;
2 |
3 | ///
4 | /// Options controlling multitenancy behavior at startup.
5 | ///
6 | public sealed class MultitenancyOptions
7 | {
8 | ///
9 | /// When true, runs per-tenant migrations and seeding for all registered IDbInitializer
10 | /// implementations during UseHeroMultiTenantDatabases. Recommended for development and demos.
11 | /// In production, prefer running migrations explicitly and leaving this disabled for faster startup.
12 | ///
13 | public bool RunTenantMigrationsOnStartup { get; set; }
14 |
15 | ///
16 | /// When true, enqueues tenant provisioning (migrate/seed) jobs on startup for tenants that have not completed provisioning.
17 | /// Useful to ensure the root tenant is provisioned automatically on first run when startup migrations are disabled.
18 | ///
19 | public bool AutoProvisionOnStartup { get; set; } = true;
20 | }
21 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoleById/GetRoleByIdEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRole;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoleById;
10 |
11 | public static class GetRoleByIdEndpoint
12 | {
13 | public static RouteHandlerBuilder MapGetRoleByIdEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/roles/{id:guid}", (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | mediator.Send(new GetRoleQuery(id), cancellationToken))
17 | .WithName("GetRole")
18 | .WithSummary("Get role by ID")
19 | .RequirePermission(IdentityPermissionConstants.Roles.View)
20 | .WithDescription("Retrieve details of a specific role by its unique identifier.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserById/GetUserByIdEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUser;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserById;
10 |
11 | public static class GetUserByIdEndpoint
12 | {
13 | internal static RouteHandlerBuilder MapGetUserByIdEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/users/{id:guid}", (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | mediator.Send(new GetUserQuery(id), cancellationToken))
17 | .WithName("GetUser")
18 | .WithSummary("Get user by ID")
19 | .RequirePermission(IdentityPermissionConstants.Users.View)
20 | .WithDescription("Retrieve a user's profile details by unique user identifier.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ConfirmEmail/ConfirmEmailEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.v1.Users.ConfirmEmail;
2 | using Mediator;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Routing;
6 |
7 | namespace FSH.Modules.Identity.Features.v1.Users.ConfirmEmail;
8 |
9 | public static class ConfirmEmailEndpoint
10 | {
11 | internal static RouteHandlerBuilder MapConfirmEmailEndpoint(this IEndpointRouteBuilder endpoints)
12 | {
13 | return endpoints.MapGet("/confirm-email", async (string userId, string code, string tenant, IMediator mediator, CancellationToken cancellationToken) =>
14 | {
15 | var result = await mediator.Send(new ConfirmEmailCommand(userId, code, tenant), cancellationToken);
16 | return Results.Ok(result);
17 | })
18 | .WithName("ConfirmEmail")
19 | .WithSummary("Confirm user email")
20 | .WithDescription("Confirm a user's email address.")
21 | .AllowAnonymous();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/FshUser.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Identity;
2 | using FSH.Modules.Identity.Features.v1.Users.PasswordHistory;
3 |
4 | namespace FSH.Modules.Identity.Features.v1.Users;
5 |
6 | public class FshUser : IdentityUser
7 | {
8 | public string? FirstName { get; set; }
9 | public string? LastName { get; set; }
10 | public Uri? ImageUrl { get; set; }
11 | public bool IsActive { get; set; }
12 | public string? RefreshToken { get; set; }
13 | public DateTime RefreshTokenExpiryTime { get; set; }
14 |
15 | public string? ObjectId { get; set; }
16 |
17 | /// Timestamp when the user last changed their password
18 | public DateTime LastPasswordChangeDate { get; set; } = DateTime.UtcNow;
19 |
20 | // Navigation property for password history
21 | public virtual ICollection PasswordHistories { get; set; } = new List();
22 | }
--------------------------------------------------------------------------------
/src/Tests/Multitenacy.Tests/Multitenacy.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net10.0
4 | false
5 | false
6 | enable
7 | enable
8 | $(NoWarn);S125;S3261
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Persistence/AuditRecordConfiguration.cs:
--------------------------------------------------------------------------------
1 | using Finbuckle.MultiTenant.EntityFrameworkCore.Extensions;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
4 |
5 | namespace FSH.Modules.Auditing.Persistence;
6 |
7 | public class AuditRecordConfiguration : IEntityTypeConfiguration
8 | {
9 | public void Configure(EntityTypeBuilder builder)
10 | {
11 | ArgumentNullException.ThrowIfNull(builder);
12 | builder.ToTable("AuditRecords", "audit");
13 | builder.IsMultiTenant();
14 | builder.HasKey(x => x.Id);
15 | builder.Property(x => x.EventType).HasConversion();
16 | builder.Property(x => x.Severity).HasConversion();
17 | builder.Property(x => x.Tags).HasConversion();
18 | builder.Property(x => x.PayloadJson).HasColumnType("jsonb");
19 | builder.HasIndex(x => x.TenantId);
20 | builder.HasIndex(x => x.EventType);
21 | builder.HasIndex(x => x.OccurredAtUtc);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/GetUserRoles/GetUserRolesEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Users.GetUserRoles;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Users.GetUserRoles;
10 |
11 | public static class GetUserRolesEndpoint
12 | {
13 | internal static RouteHandlerBuilder MapGetUserRolesEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/users/{id:guid}/roles", (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | mediator.Send(new GetUserRolesQuery(id), cancellationToken))
17 | .WithName("GetUserRoles")
18 | .WithSummary("Get user roles")
19 | .RequirePermission(IdentityPermissionConstants.Users.View)
20 | .WithDescription("Retrieve the roles assigned to a specific user.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Services/CookieAuthenticationStateProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Authorization;
2 | using Microsoft.AspNetCore.Components.Server;
3 |
4 | namespace FSH.Playground.Blazor.Services;
5 |
6 | ///
7 | /// Simple authentication state provider that reads from cookie authentication.
8 | /// Uses the built-in ServerAuthenticationStateProvider which automatically reads HttpContext.User.
9 | ///
10 | ///
11 | /// This class intentionally has no custom logic - it inherits all behavior from ServerAuthenticationStateProvider,
12 | /// which automatically reads from HttpContext.User populated by the ASP.NET Core Cookie Authentication middleware.
13 | /// The class exists to provide a named type for DI registration and potential future customization.
14 | ///
15 | #pragma warning disable S2094 // Classes should not be empty - intentionally inherits all behavior from base class
16 | internal sealed class CookieAuthenticationStateProvider : ServerAuthenticationStateProvider;
17 | #pragma warning restore S2094
18 |
--------------------------------------------------------------------------------
/terraform/modules/alb/outputs.tf:
--------------------------------------------------------------------------------
1 | output "arn" {
2 | description = "The ARN of the load balancer"
3 | value = aws_lb.this.arn
4 | }
5 |
6 | output "id" {
7 | description = "The ID of the load balancer"
8 | value = aws_lb.this.id
9 | }
10 |
11 | output "dns_name" {
12 | description = "The DNS name of the load balancer"
13 | value = aws_lb.this.dns_name
14 | }
15 |
16 | output "zone_id" {
17 | description = "The canonical hosted zone ID of the load balancer"
18 | value = aws_lb.this.zone_id
19 | }
20 |
21 | output "http_listener_arn" {
22 | description = "The ARN of the HTTP listener"
23 | value = aws_lb_listener.http.arn
24 | }
25 |
26 | output "https_listener_arn" {
27 | description = "The ARN of the HTTPS listener (if enabled)"
28 | value = var.enable_https ? aws_lb_listener.https[0].arn : null
29 | }
30 |
31 | output "listener_arn" {
32 | description = "The ARN of the primary listener (HTTPS if enabled, otherwise HTTP)"
33 | value = var.enable_https ? aws_lb_listener.https[0].arn : aws_lb_listener.http.arn
34 | }
35 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Caching/Caching.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Caching
5 | FSH.Framework.Caching
6 | FullStackHero.Framework.Caching
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Core/AuditingConfigurator.cs:
--------------------------------------------------------------------------------
1 | // Add this hosted service class once in your auditing module
2 | using FSH.Modules.Auditing.Contracts;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | namespace FSH.Modules.Auditing;
6 |
7 | public sealed class AuditingConfigurator : IHostedService
8 | {
9 | private readonly IAuditPublisher _publisher;
10 | private readonly IAuditSerializer _serializer;
11 | private readonly IEnumerable _enrichers;
12 |
13 | public AuditingConfigurator(
14 | IAuditPublisher publisher,
15 | IAuditSerializer serializer,
16 | IEnumerable enrichers)
17 | {
18 | _publisher = publisher;
19 | _serializer = serializer;
20 | _enrichers = enrichers;
21 | }
22 |
23 | public Task StartAsync(CancellationToken cancellationToken)
24 | {
25 | Audit.Configure(_publisher, _serializer, _enrichers);
26 | return Task.CompletedTask;
27 | }
28 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Sessions/GetMySessions/GetMySessionsEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Sessions.GetMySessions;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Sessions.GetMySessions;
10 |
11 | public static class GetMySessionsEndpoint
12 | {
13 | internal static RouteHandlerBuilder MapGetMySessionsEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/sessions/me", (CancellationToken cancellationToken, IMediator mediator) =>
16 | mediator.Send(new GetMySessionsQuery(), cancellationToken))
17 | .WithName("GetMySessions")
18 | .WithSummary("Get current user's sessions")
19 | .RequirePermission(IdentityPermissionConstants.Sessions.View)
20 | .WithDescription("Retrieve all active sessions for the currently authenticated user.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/ChangePassword/ChangePasswordEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.v1.Users.ChangePassword;
2 | using Mediator;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Http;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.AspNetCore.Routing;
7 |
8 | namespace FSH.Modules.Identity.Features.v1.Users.ChangePassword;
9 |
10 | public static class ChangePasswordEndpoint
11 | {
12 | internal static RouteHandlerBuilder MapChangePasswordEndpoint(this IEndpointRouteBuilder endpoints)
13 | {
14 | return endpoints.MapPost("/change-password", async (
15 | [FromBody] ChangePasswordCommand command,
16 | IMediator mediator,
17 | CancellationToken cancellationToken) =>
18 | {
19 | var result = await mediator.Send(command, cancellationToken);
20 | return Results.Ok(result);
21 | })
22 | .WithName("ChangePassword")
23 | .WithSummary("Change password")
24 | .WithDescription("Change the current user's password.");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/Specifications/SpecificationOfTResult.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Persistence.Specifications;
2 | using System.Linq.Expressions;
3 |
4 | namespace FSH.Framework.Persistence;
5 |
6 | ///
7 | /// Base specification that composes a query for and
8 | /// projects it into .
9 | ///
10 | /// The root entity type.
11 | /// The projected result type.
12 | public abstract class Specification : Specification, ISpecification
13 | where T : class
14 | {
15 | public Expression> Selector { get; private set; } = default!;
16 |
17 | ///
18 | /// Configures the projection applied at the end of the query pipeline.
19 | ///
20 | /// Projection expression.
21 | protected void Select(Expression> selector)
22 | {
23 | ArgumentNullException.ThrowIfNull(selector);
24 | Selector = selector;
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Persistence/AuditDbContext.cs:
--------------------------------------------------------------------------------
1 | using Finbuckle.MultiTenant.Abstractions;
2 | using FSH.Framework.Persistence.Context;
3 | using FSH.Framework.Shared.Multitenancy;
4 | using FSH.Framework.Shared.Persistence;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.Hosting;
7 | using Microsoft.Extensions.Options;
8 |
9 | namespace FSH.Modules.Auditing.Persistence;
10 |
11 | public sealed class AuditDbContext : BaseDbContext
12 | {
13 | public AuditDbContext(
14 | IMultiTenantContextAccessor multiTenantContextAccessor,
15 | DbContextOptions options,
16 | IOptions settings,
17 | IHostEnvironment environment) : base(multiTenantContextAccessor, options, settings, environment) { }
18 |
19 | public DbSet AuditRecords => Set();
20 | protected override void OnModelCreating(ModelBuilder modelBuilder)
21 | {
22 | ArgumentNullException.ThrowIfNull(modelBuilder);
23 | modelBuilder.ApplyConfigurationsFromAssembly(typeof(AuditDbContext).Assembly);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Mailing/Extensions.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Mailing.Services;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Options;
4 |
5 | namespace FSH.Framework.Mailing;
6 |
7 | public static class Extensions
8 | {
9 | public static IServiceCollection AddHeroMailing(this IServiceCollection services)
10 | {
11 | services.AddTransient();
12 | services
13 | .AddOptions()
14 | .BindConfiguration(nameof(MailOptions))
15 | .Validate(o => !string.IsNullOrWhiteSpace(o.From), "MailOptions: From is required.")
16 | .Validate(o => !string.IsNullOrWhiteSpace(o.Host), "MailOptions: Host is required.")
17 | .Validate(o => o.Port > 0, "MailOptions: Port must be greater than zero.")
18 | .Validate(o => !string.IsNullOrWhiteSpace(o.UserName), "MailOptions: UserName is required.")
19 | .Validate(o => !string.IsNullOrWhiteSpace(o.Password), "MailOptions: Password is required.")
20 | .ValidateOnStart();
21 | return services;
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/v1/GetAudits/GetAuditsQuery.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Persistence;
2 | using FSH.Modules.Auditing.Contracts;
3 | using FSH.Modules.Auditing.Contracts.Dtos;
4 | using Mediator;
5 |
6 | namespace FSH.Modules.Auditing.Contracts.v1.GetAudits;
7 |
8 | public sealed class GetAuditsQuery : IPagedQuery, IQuery>
9 | {
10 | public int? PageNumber { get; set; }
11 |
12 | public int? PageSize { get; set; }
13 |
14 | public string? Sort { get; set; }
15 |
16 | public DateTime? FromUtc { get; set; }
17 |
18 | public DateTime? ToUtc { get; set; }
19 |
20 | public string? TenantId { get; set; }
21 |
22 | public string? UserId { get; set; }
23 |
24 | public AuditEventType? EventType { get; set; }
25 |
26 | public AuditSeverity? Severity { get; set; }
27 |
28 | public AuditTag? Tags { get; set; }
29 |
30 | public string? Source { get; set; }
31 |
32 | public string? CorrelationId { get; set; }
33 |
34 | public string? TraceId { get; set; }
35 |
36 | public string? Search { get; set; }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditById/GetAuditByIdEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Auditing.Contracts.v1.GetAuditById;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Auditing.Features.v1.GetAuditById;
10 |
11 | public static class GetAuditByIdEndpoint
12 | {
13 | public static RouteHandlerBuilder MapGetAuditByIdEndpoint(this IEndpointRouteBuilder group)
14 | {
15 | return group.MapGet(
16 | "/{id:guid}",
17 | async (Guid id, IMediator mediator, CancellationToken cancellationToken) =>
18 | await mediator.Send(new GetAuditByIdQuery(id), cancellationToken))
19 | .WithName("GetAuditById")
20 | .WithSummary("Get audit event by ID")
21 | .WithDescription("Retrieve full details for a single audit event.")
22 | .RequirePermission(AuditingPermissionConstants.View);
23 | }
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Shared/Multitenancy/MultitenancyConstants.cs:
--------------------------------------------------------------------------------
1 | namespace FSH.Framework.Shared.Multitenancy;
2 |
3 | public static class MultitenancyConstants
4 | {
5 | public static class Root
6 | {
7 | public const string Id = "root";
8 | public const string Name = "Root";
9 | public const string EmailAddress = "admin@root.com";
10 | public const string DefaultProfilePicture = "assets/defaults/profile-picture.webp";
11 | public const string Issuer = "mukesh.murugan";
12 | }
13 |
14 | public const string DefaultPassword = "123Pa$$word!";
15 | public const string Identifier = "tenant";
16 | public const string Schema = "tenant";
17 |
18 | public static class Permissions
19 | {
20 | public const string View = "Permissions.Tenants.View";
21 | public const string Create = "Permissions.Tenants.Create";
22 | public const string Update = "Permissions.Tenants.Update";
23 | public const string ViewTheme = "Permissions.Tenants.ViewTheme";
24 | public const string UpdateTheme = "Permissions.Tenants.UpdateTheme";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Modules/Multitenancy/Modules.Multitenancy/Features/v1/UpdateTenantTheme/UpdateTenantThemeCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using Finbuckle.MultiTenant.Abstractions;
2 | using FSH.Framework.Shared.Multitenancy;
3 | using FSH.Modules.Multitenancy.Contracts;
4 | using FSH.Modules.Multitenancy.Contracts.v1.UpdateTenantTheme;
5 | using Mediator;
6 |
7 | namespace FSH.Modules.Multitenancy.Features.v1.UpdateTenantTheme;
8 |
9 | public sealed class UpdateTenantThemeCommandHandler(
10 | ITenantThemeService themeService,
11 | IMultiTenantContextAccessor tenantAccessor)
12 | : ICommandHandler
13 | {
14 | public async ValueTask Handle(UpdateTenantThemeCommand command, CancellationToken cancellationToken)
15 | {
16 | ArgumentNullException.ThrowIfNull(command);
17 |
18 | var tenantId = tenantAccessor.MultiTenantContext?.TenantInfo?.Id
19 | ?? throw new InvalidOperationException("No tenant context available");
20 |
21 | await themeService.UpdateThemeAsync(tenantId, command.Theme, cancellationToken);
22 |
23 | return Unit.Value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Sessions/GetMySessions/GetMySessionsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Core.Context;
2 | using FSH.Modules.Identity.Contracts.DTOs;
3 | using FSH.Modules.Identity.Contracts.Services;
4 | using FSH.Modules.Identity.Contracts.v1.Sessions.GetMySessions;
5 | using Mediator;
6 |
7 | namespace FSH.Modules.Identity.Features.v1.Sessions.GetMySessions;
8 |
9 | public sealed class GetMySessionsQueryHandler : IQueryHandler>
10 | {
11 | private readonly ISessionService _sessionService;
12 | private readonly ICurrentUser _currentUser;
13 |
14 | public GetMySessionsQueryHandler(ISessionService sessionService, ICurrentUser currentUser)
15 | {
16 | _sessionService = sessionService;
17 | _currentUser = currentUser;
18 | }
19 |
20 | public async ValueTask> Handle(GetMySessionsQuery query, CancellationToken cancellationToken)
21 | {
22 | var userId = _currentUser.GetUserId().ToString();
23 | return await _sessionService.GetUserSessionsAsync(userId, cancellationToken);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/DeleteUser/DeleteUserEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Users.DeleteUser;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Users.DeleteUser;
10 |
11 | public static class DeleteUserEndpoint
12 | {
13 | internal static RouteHandlerBuilder MapDeleteUserEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapDelete("/users/{id:guid}", async (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | {
17 | await mediator.Send(new DeleteUserCommand(id), cancellationToken);
18 | return Results.NoContent();
19 | })
20 | .WithName("DeleteUser")
21 | .WithSummary("Delete user")
22 | .RequirePermission(IdentityPermissionConstants.Users.Delete)
23 | .WithDescription("Delete a user by unique identifier.");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Users/UpdateUser/UpdateUserCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Identity.Contracts.Services;
2 | using FSH.Modules.Identity.Contracts.v1.Users.UpdateUser;
3 | using Mediator;
4 |
5 | namespace FSH.Modules.Identity.Features.v1.Users.UpdateUser;
6 |
7 | public sealed class UpdateUserCommandHandler : ICommandHandler
8 | {
9 | private readonly IUserService _userService;
10 |
11 | public UpdateUserCommandHandler(IUserService userService)
12 | {
13 | _userService = userService;
14 | }
15 |
16 | public async ValueTask Handle(UpdateUserCommand command, CancellationToken cancellationToken)
17 | {
18 | ArgumentNullException.ThrowIfNull(command);
19 |
20 | await _userService.UpdateAsync(
21 | command.Id,
22 | command.FirstName ?? string.Empty,
23 | command.LastName ?? string.Empty,
24 | command.PhoneNumber ?? string.Empty,
25 | command.Image!,
26 | command.DeleteCurrentImage).ConfigureAwait(false);
27 |
28 | return Unit.Value;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/terraform/apps/playground/envs/prod/us-east-1/prod.us-east-1.tfvars:
--------------------------------------------------------------------------------
1 | environment = "prod"
2 | region = "us-east-1"
3 |
4 | vpc_cidr_block = "10.30.0.0/16"
5 |
6 | public_subnets = {
7 | a = {
8 | cidr_block = "10.30.0.0/24"
9 | az = "us-east-1a"
10 | }
11 | b = {
12 | cidr_block = "10.30.1.0/24"
13 | az = "us-east-1b"
14 | }
15 | }
16 |
17 | private_subnets = {
18 | a = {
19 | cidr_block = "10.30.10.0/24"
20 | az = "us-east-1a"
21 | }
22 | b = {
23 | cidr_block = "10.30.11.0/24"
24 | az = "us-east-1b"
25 | }
26 | }
27 |
28 | app_s3_bucket_name = "CHANGE_ME-app-prod-us-east-1"
29 |
30 | db_name = "fshdb"
31 | db_username = "fshadmin"
32 | db_password = "CHANGE_ME_STRONG_PASSWORD"
33 |
34 | api_container_image = "CHANGE_ME_API_IMAGE"
35 | api_container_port = 8080
36 | api_cpu = "512"
37 | api_memory = "1024"
38 | api_desired_count = 3
39 |
40 | blazor_container_image = "CHANGE_ME_BLAZOR_IMAGE"
41 | blazor_container_port = 8080
42 | blazor_cpu = "512"
43 | blazor_memory = "1024"
44 | blazor_desired_count = 3
45 |
--------------------------------------------------------------------------------
/src/Playground/Playground.Blazor/Components/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Persistence/Persistence.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FSH.Framework.Persistence
5 | FSH.Framework.Persistence
6 | FullStackHero.Framework.Persistence
7 | $(NoWarn);S4144;CS0618
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing/Features/v1/GetAudits/GetAuditsEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Auditing.Contracts.v1.GetAudits;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Routing;
9 |
10 | namespace FSH.Modules.Auditing.Features.v1.GetAudits;
11 |
12 | public static class GetAuditsEndpoint
13 | {
14 | public static RouteHandlerBuilder MapGetAuditsEndpoint(this IEndpointRouteBuilder group)
15 | {
16 | return group.MapGet(
17 | "/",
18 | async ([AsParameters] GetAuditsQuery query, IMediator mediator, CancellationToken cancellationToken) =>
19 | await mediator.Send(query, cancellationToken))
20 | .WithName("GetAudits")
21 | .WithSummary("List and search audit events")
22 | .WithDescription("Retrieve audit events with pagination and filters.")
23 | .RequirePermission(AuditingPermissionConstants.View);
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/terraform/apps/playground/envs/staging/us-east-1/staging.us-east-1.tfvars:
--------------------------------------------------------------------------------
1 | environment = "staging"
2 | region = "us-east-1"
3 |
4 | vpc_cidr_block = "10.20.0.0/16"
5 |
6 | public_subnets = {
7 | a = {
8 | cidr_block = "10.20.0.0/24"
9 | az = "us-east-1a"
10 | }
11 | b = {
12 | cidr_block = "10.20.1.0/24"
13 | az = "us-east-1b"
14 | }
15 | }
16 |
17 | private_subnets = {
18 | a = {
19 | cidr_block = "10.20.10.0/24"
20 | az = "us-east-1a"
21 | }
22 | b = {
23 | cidr_block = "10.20.11.0/24"
24 | az = "us-east-1b"
25 | }
26 | }
27 |
28 | app_s3_bucket_name = "CHANGE_ME-app-staging-us-east-1"
29 |
30 | db_name = "fshdb"
31 | db_username = "fshadmin"
32 | db_password = "CHANGE_ME_STRONG_PASSWORD"
33 |
34 | api_container_image = "CHANGE_ME_API_IMAGE"
35 | api_container_port = 8080
36 | api_cpu = "256"
37 | api_memory = "512"
38 | api_desired_count = 2
39 |
40 | blazor_container_image = "CHANGE_ME_BLAZOR_IMAGE"
41 | blazor_container_port = 8080
42 | blazor_cpu = "256"
43 | blazor_memory = "512"
44 | blazor_desired_count = 2
45 |
--------------------------------------------------------------------------------
/src/BuildingBlocks/Eventing/Eventing.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net10.0
5 | enable
6 | enable
7 | FSH.Framework.Eventing
8 | FSH.Framework.Eventing
9 | FullStackHero.Framework.Eventing
10 | $(NoWarn);CA1711;CA1716;CA1031;S2139;S1066
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/DeleteRole/DeleteRoleEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.DeleteRole;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Roles.DeleteRole;
10 |
11 | public static class DeleteRoleEndpoint
12 | {
13 | public static RouteHandlerBuilder MapDeleteRoleEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapDelete("/roles/{id:guid}", async (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | {
17 | await mediator.Send(new DeleteRoleCommand(id), cancellationToken);
18 | return Results.NoContent();
19 | })
20 | .WithName("DeleteRole")
21 | .WithSummary("Delete role by ID")
22 | .RequirePermission(IdentityPermissionConstants.Roles.Delete)
23 | .WithDescription("Remove an existing role by its unique identifier.");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/GetRoleWithPermissions/GetRolePermissionsEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.GetRoleWithPermissions;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Routing;
8 |
9 | namespace FSH.Modules.Identity.Features.v1.Roles.GetRoleWithPermissions;
10 |
11 | public static class GetRolePermissionsEndpoint
12 | {
13 | public static RouteHandlerBuilder MapGetRolePermissionsEndpoint(this IEndpointRouteBuilder endpoints)
14 | {
15 | return endpoints.MapGet("/{id:guid}/permissions", (string id, IMediator mediator, CancellationToken cancellationToken) =>
16 | mediator.Send(new GetRoleWithPermissionsQuery(id), cancellationToken))
17 | .WithName("GetRolePermissions")
18 | .WithSummary("Get role permissions")
19 | .RequirePermission(IdentityPermissionConstants.Roles.View)
20 | .WithDescription("Retrieve a role along with its assigned permissions.");
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Modules/Identity/Modules.Identity/Features/v1/Roles/UpsertRole/CreateOrUpdateRoleEndpoint.cs:
--------------------------------------------------------------------------------
1 | using FSH.Framework.Shared.Identity;
2 | using FSH.Framework.Shared.Identity.Authorization;
3 | using FSH.Modules.Identity.Contracts.v1.Roles.UpsertRole;
4 | using Mediator;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.AspNetCore.Routing;
9 |
10 | namespace FSH.Modules.Identity.Features.v1.Roles.UpsertRole;
11 |
12 | public static class CreateOrUpdateRoleEndpoint
13 | {
14 | public static RouteHandlerBuilder MapCreateOrUpdateRoleEndpoint(this IEndpointRouteBuilder endpoints)
15 | {
16 | return endpoints.MapPost("/roles", (IMediator mediator, [FromBody] UpsertRoleCommand request, CancellationToken cancellationToken) =>
17 | mediator.Send(request, cancellationToken))
18 | .WithName("CreateOrUpdateRole")
19 | .WithSummary("Create or update role")
20 | .RequirePermission(IdentityPermissionConstants.Roles.Create)
21 | .WithDescription("Create a new role or update an existing role's name and description.");
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Modules/Auditing/Modules.Auditing.Contracts/Dtos/AuditDetailDto.cs:
--------------------------------------------------------------------------------
1 | using FSH.Modules.Auditing.Contracts;
2 | using System.Text.Json;
3 |
4 | namespace FSH.Modules.Auditing.Contracts.Dtos;
5 |
6 | public sealed class AuditDetailDto
7 | {
8 | public Guid Id { get; set; }
9 |
10 | public DateTime OccurredAtUtc { get; set; }
11 |
12 | public DateTime ReceivedAtUtc { get; set; }
13 |
14 | public AuditEventType EventType { get; set; }
15 |
16 | public AuditSeverity Severity { get; set; }
17 |
18 | public string? TenantId { get; set; }
19 |
20 | public string? UserId { get; set; }
21 |
22 | public string? UserName { get; set; }
23 |
24 | public string? TraceId { get; set; }
25 |
26 | public string? SpanId { get; set; }
27 |
28 | public string? CorrelationId { get; set; }
29 |
30 | public string? RequestId { get; set; }
31 |
32 | public string? Source { get; set; }
33 |
34 | public AuditTag Tags { get; set; }
35 |
36 | ///
37 | /// Masked, deserialized payload. Serialized back to JSON for clients.
38 | ///
39 | public JsonElement Payload { get; set; }
40 | }
41 |
42 |
--------------------------------------------------------------------------------