├── .config └── dotnet-tools.json ├── .csharpierrc ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── core_build_publish.yml │ └── docs.yml ├── .gitignore ├── .readthedocs.yaml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── Directory.Build.props ├── Directory.Packages.props ├── LICENSE ├── LeanCode.CoreLibrary.sln ├── README.md ├── cloud-tests-infrastructure ├── .gitignore ├── .terraform.lock.hcl ├── main.tf ├── outputs.tf ├── postgres.tf ├── readme.md ├── storage.tf └── variables.tf ├── docs ├── .markdownlint.jsonc ├── .vscode │ ├── extensions.json │ └── settings.json ├── README.md ├── cqrs │ ├── authorization │ │ └── index.md │ ├── command │ │ └── index.md │ ├── index.md │ ├── local_execution │ │ └── index.md │ ├── operation │ │ └── index.md │ ├── pipeline │ │ ├── adding_custom_middlewares.md │ │ ├── avoid_commiting_transactions_in_handlers.md │ │ └── index.md │ ├── query │ │ └── index.md │ └── validation │ │ └── index.md ├── domain │ ├── aggregate │ │ └── index.md │ ├── domain_event │ │ └── index.md │ ├── entity │ │ └── index.md │ ├── id │ │ └── index.md │ ├── index.md │ ├── time_provider │ │ └── index.md │ └── value_object │ │ └── index.md ├── external_integrations │ ├── api_explorer │ │ └── index.md │ ├── authorization_ory_kratos │ │ ├── handling_webhooks.md │ │ └── index.md │ ├── emails_sendgrid │ │ └── index.md │ ├── feature_flags_configcat │ │ └── index.md │ ├── logging_serilog │ │ ├── index.md │ │ └── sanitization.md │ ├── messaging_masstransit │ │ ├── handling_events.md │ │ └── index.md │ ├── observability_open_telemetry │ │ └── index.md │ └── push_notifications_fcm │ │ └── index.md ├── features │ ├── audit_logs │ │ └── index.md │ ├── force_update │ │ └── index.md │ └── localization │ │ └── index.md ├── img │ ├── leancode_logo_black.png │ └── leancode_logo_white.png ├── quick_start │ └── index.md ├── requirements.txt ├── stylesheets │ └── extra.css ├── tests │ ├── faking_time │ │ └── index.md │ └── integration_tests │ │ └── index.md └── v8_migration_guide │ └── index.md ├── global.json ├── mkdocs.yml ├── src ├── CQRS │ ├── LeanCode.CQRS.Annotations │ │ ├── LeanCode.CQRS.Annotations.csproj │ │ ├── LeanCode.Contracts.pb │ │ └── PathAliasAttribute.cs │ ├── LeanCode.CQRS.AspNetCore │ │ ├── CQRSApplicationBuilder.cs │ │ ├── CQRSEndpointRouteBuilderExtensions.cs │ │ ├── CQRSEndpointsDataSource.cs │ │ ├── CQRSMetrics.cs │ │ ├── CommandValidatorResolver.cs │ │ ├── Internals.cs │ │ ├── LeanCode.CQRS.AspNetCore.csproj │ │ ├── Local │ │ │ ├── Context │ │ │ │ ├── LocalCallContext.cs │ │ │ │ ├── LocalCallLifetimeFeature.cs │ │ │ │ ├── LocalCallServiceProvidersFeature.cs │ │ │ │ ├── LocalHttpRequest.cs │ │ │ │ ├── NullConnectionInfo.cs │ │ │ │ ├── NullEndpointFeature.cs │ │ │ │ ├── NullHeaderDictionary.cs │ │ │ │ ├── NullHttpResponse.cs │ │ │ │ ├── NullRequestCookieCollection.cs │ │ │ │ ├── NullResponseCookies.cs │ │ │ │ ├── NullSession.cs │ │ │ │ └── NullWebSocketManager.cs │ │ │ ├── ILocalCommandExecutor.cs │ │ │ ├── ILocalOperationExecutor.cs │ │ │ ├── ILocalQueryExecutor.cs │ │ │ ├── MiddlewareBasedLocalCommandExecutor.cs │ │ │ ├── MiddlewareBasedLocalExecutor.cs │ │ │ ├── MiddlewareBasedLocalOperationExecutor.cs │ │ │ ├── MiddlewareBasedLocalQueryExecutor.cs │ │ │ ├── UnauthenticatedCQRSRequestException.cs │ │ │ ├── UnauthorizedCQRSRequestException.cs │ │ │ └── UnknownStatusCodeException.cs │ │ ├── Middleware │ │ │ ├── CQRSExceptionTranslationMiddleware.cs │ │ │ ├── CQRSMiddleware.cs │ │ │ ├── CQRSPipelineFinalizer.cs │ │ │ ├── CQRSSecurityMiddleware.cs │ │ │ ├── CQRSValidationMiddleware.cs │ │ │ ├── NonProductionResponseLoggerMiddleware.cs │ │ │ └── ResponseLoggerMiddleware.cs │ │ ├── ObjectExecutorFactory.cs │ │ ├── Registration │ │ │ ├── ApiDescriptionTags.cs │ │ │ ├── CQRSApiDescriptionConfiguration.cs │ │ │ ├── CQRSApiDescriptionProvider.cs │ │ │ ├── CQRSObjectsRegistrationSource.cs │ │ │ ├── EndpointMetadata.cs │ │ │ ├── ICQRSObjectSource.cs │ │ │ └── ServiceCollectionRegistrationExtensions.cs │ │ ├── Serialization │ │ │ └── ISerializer.cs │ │ └── ServiceCollectionCQRSExtensions.cs │ ├── LeanCode.CQRS.Execution │ │ ├── CQRSObjectMetadata.cs │ │ ├── CQRSRequestPayload.cs │ │ ├── CommandExecutionInvalidException.cs │ │ ├── CommandHandlerNotFoundException.cs │ │ ├── ExecutionResult.cs │ │ ├── HttpContextExtensions.cs │ │ ├── ICommandHandler.cs │ │ ├── IOperationHandler.cs │ │ ├── IQueryHandler.cs │ │ ├── LeanCode.CQRS.Execution.csproj │ │ ├── OperationHandlerNotFoundException.cs │ │ └── QueryHandlerNotFoundException.cs │ ├── LeanCode.CQRS.MassTransitRelay │ │ ├── AsyncEventsInterceptor.cs │ │ ├── AsyncEventsInterceptorExtensions.cs │ │ ├── BusObserverExtensions.cs │ │ ├── LeanCode.CQRS.MassTransitRelay.csproj │ │ ├── MassTransitRegistrationConfigurationExtensions.cs │ │ ├── MassTransitRelayApplicationBuilderExtensions.cs │ │ ├── MassTransitRelayServiceCollectionExtensions.cs │ │ ├── Middleware │ │ │ ├── CommitDatabaseTransactionMiddleware.cs │ │ │ ├── CorrelationFilter.cs │ │ │ ├── EventsPublisherFilter.cs │ │ │ └── EventsPublisherMiddleware.cs │ │ ├── ProbeSiteExtensions.cs │ │ ├── ScopedTypedConsumerConsumePipeSpecificationObserver.cs │ │ └── Testing │ │ │ ├── AsyncManualResetEvent.cs │ │ │ ├── ResettableBusActivityMonitor.cs │ │ │ └── ResettableBusActivityMonitorInitializer.cs │ ├── LeanCode.CQRS.RemoteHttp.Client │ │ ├── Exceptions.cs │ │ ├── HttpCommandsExecutor.cs │ │ ├── HttpOperationsExecutor.cs │ │ ├── HttpQueriesExecutor.cs │ │ ├── HttpResponseMessageExtensions.cs │ │ └── LeanCode.CQRS.RemoteHttp.Client.csproj │ ├── LeanCode.CQRS.ScopedTransaction │ │ ├── LeanCode.CQRS.ScopedTransaction.csproj │ │ ├── PessimisticConcurrencyAttribute.cs │ │ └── ScopedTransactionMiddleware.cs │ ├── LeanCode.CQRS.Security │ │ ├── ClaimsPrincipalExtensions.cs │ │ ├── CustomAuthorizer.cs │ │ ├── CustomAuthorizerNotFoundException.cs │ │ ├── DefaultPermissionAuthorizer.cs │ │ ├── Exceptions │ │ │ ├── InsufficientPermissionException.cs │ │ │ └── UnauthenticatedException.cs │ │ ├── IAuthorizerResolver.cs │ │ ├── IRoleRegistration.cs │ │ ├── LeanCode.CQRS.Security.csproj │ │ ├── Role.cs │ │ └── RoleRegistry.cs │ ├── LeanCode.CQRS.Validation.Fluent │ │ ├── AdapterLoader.cs │ │ ├── FluentValidationCommandValidatorAdapter.cs │ │ ├── FluentValidationErrorState.cs │ │ ├── FluentValidationExtensions.cs │ │ ├── LeanCode.CQRS.Validation.Fluent.csproj │ │ ├── ServiceProviderExtensions.cs │ │ └── ValidationContextExtensions.cs │ └── LeanCode.CQRS.Validation │ │ ├── ICommandValidator.cs │ │ ├── ICommandValidatorResolver.cs │ │ └── LeanCode.CQRS.Validation.csproj ├── Core │ ├── LeanCode.Components.Autofac │ │ ├── IAppModule.cs │ │ └── LeanCode.Components.Autofac.csproj │ ├── LeanCode.Components │ │ ├── LeanCode.Components.csproj │ │ ├── ServiceCollectionExtensions.cs │ │ └── TypesCatalog.cs │ ├── LeanCode.Startup.Autofac │ │ ├── LeanCode.Startup.Autofac.csproj │ │ ├── LeanProgram.cs │ │ └── LeanStartup.cs │ └── LeanCode.Startup.MicrosoftDI │ │ ├── LeanCode.Startup.MicrosoftDI.csproj │ │ ├── LeanProgram.cs │ │ └── LeanStartup.cs ├── Directory.Build.props ├── Domain │ ├── LeanCode.DomainModels.EF │ │ ├── CachingEFRepository.cs │ │ ├── CollectionExtensions.cs │ │ ├── DateTimeOnlyConverter.cs │ │ ├── DbContextExtensions.cs │ │ ├── EFRepository.cs │ │ ├── IdConverter.cs │ │ ├── LeanCode.DomainModels.EF.csproj │ │ ├── ModelBuilderExtensions.cs │ │ ├── PropertiesConfigurationBuilderExtensions.cs │ │ ├── PropertyBuilderExtensions.cs │ │ ├── QueryExpressionVisitorInterceptor.cs │ │ ├── SimpleEFRepository.cs │ │ ├── TimestampTzExpressionInterceptor.cs │ │ └── TypedIdConverter.cs │ ├── LeanCode.DomainModels.Generators │ │ ├── AnalyzerReleases.Shipped.md │ │ ├── IdSource.cs │ │ ├── LeanCode.DomainModels.Generators.csproj │ │ ├── TypedIdData.cs │ │ ├── TypedIdFormat.cs │ │ └── TypedIdGenerator.cs │ ├── LeanCode.DomainModels │ │ ├── DataAccess │ │ │ ├── IRepository.cs │ │ │ └── IRepositoryExtensions.cs │ │ ├── Ids │ │ │ ├── ITypedId.cs │ │ │ ├── TypedIdAttribute.cs │ │ │ └── TypedIdConverter.cs │ │ ├── LeanCode.DomainModels.csproj │ │ ├── Model │ │ │ ├── DomainEvents.cs │ │ │ ├── IAggregateRoot.cs │ │ │ ├── IDomainEvent.cs │ │ │ ├── IDomainEventInterceptor.cs │ │ │ ├── IEntity.cs │ │ │ ├── IOptimisticConcurrency.cs │ │ │ ├── ISoftDeletable.cs │ │ │ ├── Id.cs │ │ │ ├── SId.cs │ │ │ ├── TimestampTz.cs │ │ │ ├── TypedIdConverterAttribute.cs │ │ │ └── ValueObject.cs │ │ └── Ulids │ │ │ ├── Ulid.cs │ │ │ ├── UlidJsonConverter.cs │ │ │ └── UlidRandomProvider.cs │ └── LeanCode.TimeProvider │ │ ├── AsyncLocalTimeProvider.cs │ │ ├── LeanCode.TimeProvider.csproj │ │ ├── Time.cs │ │ └── UtcSystemTimeProvider.cs ├── Helpers │ ├── LeanCode.UrlHelper │ │ ├── LeanCode.UrlHelper.csproj │ │ └── UrlHelper.cs │ └── LeanCode.UserIdExtractors │ │ ├── Extractors │ │ ├── GuidUserIdExtractor.cs │ │ ├── PrefixedTypedUserIdExtractor.cs │ │ ├── RawTypedUserIdExtractor.cs │ │ └── StringUserIdExtractor.cs │ │ ├── IUserIdExtractor.cs │ │ ├── LeanCode.UserIdExtractors.csproj │ │ └── ServiceProviderExtensions.cs ├── Infrastructure │ ├── LeanCode.AI.Contracts │ │ ├── LeanCode.AI.Contracts.csproj │ │ ├── McpToolAttribute.cs │ │ └── McpToolHints.cs │ ├── LeanCode.AI.McpServer │ │ ├── CQRSAIFunction.cs │ │ ├── CQRSObjectEnumerator.cs │ │ ├── LeanCode.AI.McpServer.csproj │ │ └── McpServerBuilderExtensions.cs │ ├── LeanCode.AuditLogs │ │ ├── AppicationBuilderExtensions.cs │ │ ├── AuditLogsConsumer.cs │ │ ├── AuditLogsExtensions.cs │ │ ├── AuditLogsFilter.cs │ │ ├── AuditLogsMiddleware.cs │ │ ├── AuditLogsPublisher.cs │ │ ├── AzureBlobAuditLogStorage.cs │ │ ├── ChangedEntitiesExtractor.cs │ │ ├── EntityData.cs │ │ ├── IAuditLogStorage.cs │ │ ├── LeanCode.AuditLogs.csproj │ │ └── MassTransintRegistrationConfigurationExtensions.cs │ ├── LeanCode.AzureIdentity │ │ ├── AzureCredentialConfiguration.cs │ │ ├── DefaultLeanCodeCredential.cs │ │ ├── IHostBuilderExtensions.cs │ │ └── LeanCode.AzureIdentity.csproj │ ├── LeanCode.ClientCredentialsHandler │ │ ├── ClientCredentialsConfiguration.cs │ │ ├── ClientCredentialsHandler.cs │ │ └── LeanCode.ClientCredentialsHandler.csproj │ ├── LeanCode.ConfigCat │ │ ├── ConfigCatExtensions.cs │ │ ├── ConfigCatInitializer.cs │ │ ├── ConfigCatOptions.cs │ │ ├── ConfigCatToMSLoggerAdapter.cs │ │ └── LeanCode.ConfigCat.csproj │ ├── LeanCode.Dapper │ │ ├── DbContextExtensions.cs │ │ ├── LeanCode.Dapper.csproj │ │ ├── RawQueryValidator.cs │ │ └── RawSqlQueryAttribute.cs │ ├── LeanCode.EFMigrator │ │ ├── BaseFactory.cs │ │ ├── LeanCode.EFMigrator.csproj │ │ ├── MigrationsConfig.cs │ │ └── Migrator.cs │ ├── LeanCode.Firebase.FCM │ │ ├── FCMClient.cs │ │ ├── FCMSendException.cs │ │ ├── FCMServiceCollectionExtensions.cs │ │ ├── IPushNotificationTokenStore.cs │ │ ├── LeanCode.Firebase.FCM.csproj │ │ ├── LocalizedNotification.cs │ │ ├── ModelBuilderExtensions.cs │ │ ├── NotificationDataConverter.cs │ │ ├── Notifications.cs │ │ ├── PushNotificationTokenEntity.cs │ │ └── PushNotificationTokenStore.cs │ ├── LeanCode.Firebase.Firestore │ │ ├── FirestoreDatabase.cs │ │ ├── FirestoreModule.cs │ │ └── LeanCode.Firebase.Firestore.csproj │ ├── LeanCode.Firebase │ │ ├── FirebaseConfiguration.cs │ │ └── LeanCode.Firebase.csproj │ ├── LeanCode.ForceUpdate.Contracts │ │ ├── LeanCode.Contracts.pb │ │ ├── LeanCode.ForceUpdate.Contracts.csproj │ │ └── VersionSupport.cs │ ├── LeanCode.ForceUpdate │ │ ├── CQRSServicesBuilderCQRSExtensions.cs │ │ ├── LeanCode.ForceUpdate.Services │ │ │ ├── CQRS │ │ │ │ └── VersionSupportQH.cs │ │ │ └── VersionHandler.cs │ │ ├── LeanCode.ForceUpdate.csproj │ │ └── VersionsConfiguration.cs │ ├── LeanCode.Kratos │ │ ├── KratosAuthenticationHandler.cs │ │ ├── KratosAuthenticationOptions.cs │ │ ├── KratosDefaults.cs │ │ ├── KratosExtensions.cs │ │ ├── KratosWebHookHandlerBase.cs │ │ ├── LeanCode.Kratos.csproj │ │ └── Model │ │ │ ├── Identity.cs │ │ │ └── IdentityAddress.cs │ ├── LeanCode.Localization │ │ ├── LeanCode.Localization.csproj │ │ ├── LocalizationConfiguration.cs │ │ ├── LocalizationServiceProviderExtensions.cs │ │ ├── LocalizedResourceNotFoundException.cs │ │ └── StringLocalizers │ │ │ ├── IStringLocalizer.cs │ │ │ ├── IStringLocalizerExtensions.cs │ │ │ └── ResourceManagerStringLocalizer.cs │ ├── LeanCode.Logging.AspNetCore │ │ ├── IHostBuilderExtensions.cs │ │ ├── LeanCode.Logging.AspNetCore.csproj │ │ ├── LeanCodeLoggingBuilderExtensions.cs │ │ ├── SerilogExtensions.cs │ │ └── UserIdLogsCorrelationMiddleware.cs │ ├── LeanCode.Logging │ │ ├── BaseSanitizer.cs │ │ ├── ContextualLogger.cs │ │ ├── ILogger.cs │ │ ├── LeanCode.Logging.csproj │ │ ├── LeanCodeLoggingBuilderExtensions.cs │ │ └── NullLogger.cs │ ├── LeanCode.Mixpanel │ │ ├── IMixpanelEvent.cs │ │ ├── LeanCode.Mixpanel.csproj │ │ ├── MixpanelAnalytics.cs │ │ ├── MixpanelConfiguration.cs │ │ └── MixpanelServiceCollectionExtensions.cs │ ├── LeanCode.Npgsql.ActiveDirectory │ │ ├── LeanCode.Npgsql.ActiveDirectory.csproj │ │ └── NpgsqlDataSourceBuilderExtensions.cs │ ├── LeanCode.OpenTelemetry │ │ ├── ActivityLogsEnricher.cs │ │ ├── Datadog │ │ │ └── DatadogIdConverter.cs │ │ ├── IdentityTraceAttributesFromBaggageProcessor.cs │ │ ├── IdentityTraceAttributesMiddleware.cs │ │ ├── IdentityTraceBaggageHelpers.cs │ │ ├── LeanCode.OpenTelemetry.csproj │ │ ├── LeanCodeActivitySource.cs │ │ ├── LeanCodeMetrics.cs │ │ ├── MeterBuilderExtensions.cs │ │ └── TracerBuilderExtensions.cs │ ├── LeanCode.PeriodicService │ │ ├── IPeriodicAction.cs │ │ ├── LeanCode.PeriodicService.csproj │ │ ├── PeriodicHostedService.cs │ │ └── ServiceCollectionExtensions.cs │ ├── LeanCode.SendGrid │ │ ├── EnumerableAsyncExtensions.cs │ │ ├── LeanCode.SendGrid.csproj │ │ ├── SendGridException.cs │ │ ├── SendGridLocalizedRazorMessage.cs │ │ ├── SendGridRazorClient.cs │ │ ├── SendGridRazorMessage.cs │ │ ├── SendGridRazorMessageExtensions.cs │ │ ├── SendGridResponse.cs │ │ └── SendGridServiceCollectionExtensions.cs │ ├── LeanCode.Serialization │ │ ├── JsonConverters │ │ │ ├── JsonLaxDateOnlyConverter.cs │ │ │ ├── JsonLaxDateTimeOffsetConverter.cs │ │ │ ├── JsonLaxTimeOnlyConverter.cs │ │ │ └── JsonStringEnumConverters.cs │ │ └── LeanCode.Serialization.csproj │ ├── LeanCode.SmsSender │ │ ├── Exceptions │ │ │ └── Exceptions.cs │ │ ├── ISmsSender.cs │ │ ├── LeanCode.SmsSender.csproj │ │ ├── SmsApiClient.cs │ │ ├── SmsApiConfiguration.cs │ │ └── SmsSenderServiceCollectionExtensions.cs │ ├── LeanCode.ViewRenderer.Razor │ │ ├── CompiledViewsCache.cs │ │ ├── Extensions │ │ │ ├── Layout.cs │ │ │ ├── LayoutDirectivePass.cs │ │ │ └── LayoutNode.cs │ │ ├── Internals.cs │ │ ├── LeanCode.ViewRenderer.Razor.csproj │ │ ├── RazorViewRenderer.cs │ │ ├── RazorViewRendererOptions.cs │ │ ├── RazorViewRendererServiceCollectionExtensions.cs │ │ ├── ViewBase │ │ │ ├── AttributeValue.cs │ │ │ ├── BaseView.cs │ │ │ └── HelperResult.cs │ │ ├── ViewCompiler.cs │ │ └── ViewLocator.cs │ └── LeanCode.ViewRenderer │ │ ├── CompilationFailedException.cs │ │ ├── IViewRenderer.cs │ │ ├── LeanCode.ViewRenderer.csproj │ │ └── ViewNotFoundException.cs ├── Testing │ ├── LeanCode.DomainModels.EventsExecution.TestHelpers │ │ ├── EventsInterceptor.cs │ │ └── LeanCode.DomainModels.EventsExecution.TestHelpers.csproj │ ├── LeanCode.IntegrationTestHelpers │ │ ├── ConfigurationOverrides.cs │ │ ├── DbContextInitializer.cs │ │ ├── HttpClientExtensions.cs │ │ ├── LeanCode.IntegrationTestHelpers.csproj │ │ ├── LeanCodeTestFactory.cs │ │ ├── TestAuthenticationHandler.cs │ │ └── TestConnectionString.cs │ └── LeanCode.TimeProvider.TestHelpers │ │ ├── LeanCode.TimeProvider.TestHelpers.csproj │ │ └── TestTimeProvider.cs └── Tools │ └── LeanCode.CodeAnalysis │ ├── AnalyzerReleases.Shipped.md │ ├── Analyzers │ ├── CancellationTokensShouldFollowNamingConvention.cs │ ├── EnsureCQRSHandlersAreInProperNamespace.cs │ ├── EnsureCQRSHandlersFollowNamingConvention.cs │ ├── EnsureCommandValidatorsFollowNamingConvention.cs │ ├── EnsureCommandsQueriesAndOperationsHaveAuthorizers.cs │ ├── NamedTypeSymbolExtensions.cs │ └── SuggestCommandsHaveValidators.cs │ ├── CodeActions │ ├── AddAuthorizationAttributeCodeAction.cs │ ├── AddCommandValidatorCodeAction.cs │ ├── FixCQRSHandlerNamespaceCodeAction.cs │ ├── FixCQRSHandlerNamingCodeAction.cs │ ├── FixCancellationTokenNamingCodeAction.cs │ ├── FixCommandValidatorNamingCodeAction.cs │ └── Helpers.cs │ ├── CodeFixProviders │ ├── AddAuthorizationAttributeCodeFixProvider.cs │ ├── AddCommandValidatorCodeFixProvider.cs │ ├── FixCQRSHandlerNamespaceCodeFixProvider.cs │ ├── FixCQRSHandlerNamingCodeFixProvider.cs │ ├── FixCancellationTokenNamingCodeFixProvider.cs │ └── FixCommandValidatorNamingCodeFixProvider.cs │ ├── DiagnosticsIds.cs │ ├── LeanCode.CodeAnalysis.csproj │ └── local-debug.txt ├── test-bed ├── Api │ ├── TestCommand.cs │ └── TestQuery.cs ├── LeanCode.TestBed.csproj └── Program.cs └── test ├── CQRS ├── LeanCode.CQRS.AspNetCore.Tests.Integration │ ├── CustomAuthorizer.cs │ ├── HttpContextCustomAuthorizer.cs │ ├── LeanCode.CQRS.AspNetCore.Tests.Integration.csproj │ ├── RemoteCQRSCommandsTests.cs │ ├── RemoteCQRSOperationsTests.cs │ ├── RemoteCQRSQueriesTests.cs │ ├── RemoteCQRSTestsBase.cs │ ├── TestCommand.cs │ ├── TestOperation.cs │ └── TestQuery.cs ├── LeanCode.CQRS.AspNetCore.Tests │ ├── CQRSEndpointsDataSourceTests.cs │ ├── CQRSObjectsRegistrationSourceTests.cs │ ├── CQRSServicesBuilderTests.cs │ ├── CommandValidatorResolverTests.cs │ ├── LeanCode.CQRS.AspNetCore.Tests.csproj │ ├── Local │ │ ├── Context │ │ │ ├── LocalCallContextTests.cs │ │ │ ├── LocalCallLifetimeFeatureTests.cs │ │ │ ├── LocalCallServiceProvidersFeatureTests.cs │ │ │ ├── LocalHttpRequestTests.cs │ │ │ ├── NullConnectionInfoTests.cs │ │ │ ├── NullEndpointFeatureTests.cs │ │ │ ├── NullHeaderDictionaryTests.cs │ │ │ ├── NullHttpResponseTests.cs │ │ │ ├── NullRequestCookieCollectionTests.cs │ │ │ ├── NullResponseCookiesTests.cs │ │ │ ├── NullSessionTests.cs │ │ │ └── NullWebSocketManagerTests.cs │ │ ├── MiddlewareBasedExecutorIntegrationTests.cs │ │ ├── MiddlewareBasedLocalExecutorTests.cs │ │ └── MiddlewaresForLocalExecutionTests.cs │ ├── Middleware │ │ ├── CQRSExceptionTranslationMiddlewareTests.cs │ │ ├── CQRSMiddlewareTestBase.cs │ │ ├── CQRSMiddlewareTests.cs │ │ ├── CQRSSecurityMiddlewareTests.cs │ │ ├── CQRSValidationMiddlewareTests.cs │ │ └── TestHelpers.cs │ ├── ObjectExecutorFactoryTests.cs │ ├── Registration │ │ ├── ApiDescriptionTagsTests.cs │ │ └── CQRSApiDescriptionProviderTests.cs │ ├── ServiceCollectionExtensionsTests.cs │ └── ServiceProviderRegistrationExtensionsTests.cs ├── LeanCode.CQRS.MassTransitRelay.Tests │ ├── Integration │ │ ├── HandledLog.cs │ │ ├── MassTransitIntegrationTest.cs │ │ ├── TestApp.cs │ │ ├── TestCommand.cs │ │ ├── TestConsumers.cs │ │ ├── TestDbContext.cs │ │ └── TestEvents.cs │ ├── LeanCode.CQRS.MassTransitRelay.Tests.csproj │ ├── MassTransitRegistrationConfigurationExtensionsTests.cs │ ├── Middleware │ │ └── EventsPublisherFilterTests.cs │ ├── ScopedFiltersTests.cs │ └── Testing │ │ └── ResettableBusActivityMonitorTests.cs ├── LeanCode.CQRS.RemoteHttp.Client.Tests │ ├── HttpCommandsExecutorTests.cs │ ├── HttpOperationsExecutorTests.cs │ ├── HttpQueriesExecutorTests.cs │ ├── LeanCode.CQRS.RemoteHttp.Client.Tests.csproj │ └── ShortcircuitingJsonHandler.cs ├── LeanCode.CQRS.Security.Tests │ ├── ClaimsPrinicipalTests.cs │ ├── CustomAuthorizerTests.cs │ └── LeanCode.CQRS.Security.Tests.csproj └── LeanCode.CQRS.Validation.Fluent.Tests │ ├── AdapterWithAsyncValidatorIntegrationTests.cs │ ├── LeanCode.CQRS.Validation.Fluent.Tests.csproj │ └── ModuleTests.cs ├── Core └── LeanCode.Components.Startup.Tests │ ├── LeanCode.Components.Startup.Tests.csproj │ └── LeanStartupTests.cs ├── Directory.Build.props ├── Domain ├── LeanCode.DomainModels.EF.Tests │ ├── CollectionExtensionsTests.cs │ ├── DateTimeOnlyConverterTests.cs │ ├── EFRepositoryTrackingCompositeTests.cs │ ├── EFRepositoryTrackingTests.cs │ ├── LeanCode.DomainModels.EF.Tests.csproj │ ├── ModelBuilderExtensionsTests.OptimisticConcurrency.cs │ ├── ModelConfigurationBuilderWrapper.cs │ ├── TimestampTzExpressionInterceptorTests.cs │ ├── TypedIdConverterTests.cs │ ├── TypedIdDatabaseIntegrationTests.cs │ └── TypedIds.cs ├── LeanCode.DomainModels.EventsExecution.TestHelpers.Tests │ ├── Events.cs │ ├── EventsInterceptorTests.RaceConds.cs │ ├── EventsInterceptorTests.cs │ └── LeanCode.DomainModels.EventsExecution.TestHelpers.Tests.csproj ├── LeanCode.DomainModels.Tests │ ├── DataAccess │ │ └── IRepositoryExtensionsTests.cs │ ├── Ids │ │ ├── GeneratorRunner.cs │ │ ├── GuidTests.cs │ │ ├── IntTests.cs │ │ ├── InvalidConstructTests.cs │ │ ├── LongTests.cs │ │ ├── PrefixedGuidTests.cs │ │ ├── PrefixedGuidVariationsTests.cs │ │ ├── PrefixedUlidTests.cs │ │ ├── PrefixedUlidVariationsTests.cs │ │ └── ValidConstructTests.cs │ ├── LeanCode.DomainModels.Tests.csproj │ ├── Model │ │ ├── SIdTests.cs │ │ ├── TimestampTzTests.cs │ │ ├── TypedIdSerializationTests.cs │ │ ├── TypedIdTests.cs │ │ └── ValueObjectTests.cs │ └── Ulids │ │ ├── UlidJsonConverterTests.cs │ │ └── UlidTests.cs └── LeanCode.TimeProvider.Tests │ ├── LeanCode.TimeProvider.Tests.csproj │ └── TimeProviderTests.cs ├── Helpers └── LeanCode.UserIdExtractors.Tests │ ├── LeanCode.UserIdExtractors.Tests.csproj │ ├── UserIdExtractorsRegistrationTests.cs │ └── UserIdExtractorsTests.cs ├── Infrastructure ├── LeanCode.AuditLogs.Tests │ ├── AuditLogsFilterTests.cs │ ├── AuditLogsIntegrationTests.cs │ ├── AuditLogsMiddlewareTests.cs │ ├── AuditLogsPublisherTests.cs │ ├── ChangedEntititesExtractorTests.cs │ ├── LeanCode.AuditLogs.Tests.csproj │ └── TestDbContext.cs ├── LeanCode.Azure.Tests │ ├── AzureStorageAuditLogIntegrationTests.cs │ ├── Env.cs │ ├── LeanCode.Azure.Tests.csproj │ └── PostgresAD │ │ └── NpgsqlActiveDirectoryAuthenticationTests.cs ├── LeanCode.AzureIdentity.Tests │ ├── AzureIdentityFact.cs │ ├── DefaultLeanCodeCredentialsTests.Authorization.cs │ ├── DefaultLeanCodeCredentialsTests.Configuration.cs │ ├── LeanCode.AzureIdentity.Tests.csproj │ └── Properties.cs ├── LeanCode.Dapper.Tests │ ├── DbContextExtensionsTests.cs │ └── LeanCode.Dapper.Tests.csproj ├── LeanCode.Firebase.FCM.Tests │ ├── FCMClientLocalizationTests.cs │ ├── FCMClientTests.cs │ ├── FCMFact.cs │ ├── LeanCode.Firebase.FCM.Tests.csproj │ ├── NotificationConversionTests.cs │ └── StubStore.cs ├── LeanCode.ForceUpdate.Tests │ ├── ForceUpdateTests.cs │ └── LeanCode.ForceUpdate.Tests.csproj ├── LeanCode.Kratos.Tests │ ├── KratosAuthenticationHandlerTests.cs │ ├── KratosWebHookHandlerTests.cs │ └── LeanCode.Kratos.Tests.csproj ├── LeanCode.Localization.Tests │ ├── Infrastructure │ │ └── LeanCode.Localization.Tests │ │ │ └── ResourceManagerStringLocalizerTests.cs │ ├── LeanCode.Localization.Tests.csproj │ ├── ResourceManagerStringLocalizerTests.pl.resx │ └── ResourceManagerStringLocalizerTests.resx ├── LeanCode.Logging.AspNetCore.Tests │ ├── LeanCode.Logging.AspNetCore.Tests.csproj │ └── LoggerInjectionTests.cs ├── LeanCode.Logging.Tests │ ├── BaseSanitizerTests.cs │ └── LeanCode.Logging.Tests.csproj ├── LeanCode.Mixpanel.Tests │ ├── LeanCode.Mixpanel.Tests.csproj │ └── MixpanelAnalyticsTests.cs ├── LeanCode.SendGrid.Tests │ ├── LeanCode.SendGrid.Tests.csproj │ └── SendGridRazorClientTests.cs ├── LeanCode.Serialization.Tests │ ├── JsonContentTests.cs │ ├── JsonLaxConvertersTests │ │ ├── JsonLaxDateOnlyConverterTests.cs │ │ ├── JsonLaxDateTimeOffsetConverterTests.cs │ │ └── JsonLaxTimeOnlyConverterTests.cs │ ├── JsonStringEnumConvertersTests.cs │ └── LeanCode.Serialization.Tests.csproj ├── LeanCode.SmsSender.Tests │ ├── LeanCode.SmsSender.Tests.csproj │ └── SmsSenderClientTests.cs └── LeanCode.ViewRenderer.Razor.Tests │ ├── CompiledViewsCacheTests.cs │ ├── LeanCode.ViewRenderer.Razor.Tests.csproj │ ├── RazorViewRendererTests.cs │ ├── ViewCompilerTests.cs │ ├── ViewLocatorTests.cs │ └── Views │ ├── A │ ├── ViewA.cshtml │ ├── ViewB.cshtml │ └── ViewC.cshtml │ ├── B │ ├── ViewA.cshtml │ ├── ViewB.cstxt │ └── ViewD.cshtml │ ├── Cache │ └── Simple.cshtml │ ├── Compiler │ ├── CompiledError.cshtml │ ├── Dotted.Name.cshtml │ ├── Layouted.cshtml │ ├── LayoutedSecondLine.cshtml │ ├── RazorError.cshtml │ └── Simple.cshtml │ └── Renderer │ ├── Functions.cshtml │ ├── HierarchicalLayout.cshtml │ ├── HierarchicalLayout1.cshtml │ ├── HierarchicalLayout2.cshtml │ ├── Layout1.cshtml │ ├── LayoutModel.cshtml │ ├── LayoutModel1.cshtml │ ├── Layouted.cshtml │ ├── Model.cshtml │ └── Simple.cshtml ├── LeanCode.IntegrationTests ├── App │ ├── AppRoles.cs │ ├── AuthConfig.cs │ ├── CQRS.cs │ ├── Entity.cs │ ├── Meeting.cs │ ├── Program.cs │ ├── Startup.cs │ └── TestDbContext.cs ├── CQRSTests.cs ├── EFRepositoryTests.cs ├── LeanCode.IntegrationTests.csproj ├── PostgresOnlyFactAttribute.cs ├── PushNotificationTokenStoreTests.cs ├── TestApp.cs ├── TestDatabaseConfig.cs ├── TimestampTzTests.cs └── docker │ ├── .env │ ├── Dockerfile │ ├── Dockerfile.watch │ └── docker-compose.yml ├── LeanCode.Test.Helpers ├── ExternalServiceFactAttribute.cs ├── IntegrationFactAttribute.cs ├── LeanCode.Test.Helpers.csproj └── LongRunningFact.cs ├── LeanCode.TestCoverage.Tests ├── LeanCode.TestCoverage.Tests.csproj └── TestCoverageTests.cs ├── Testing └── LeanCode.IntegrationTestHelpers.Tests │ ├── App │ ├── AuthQuery.cs │ ├── Command.cs │ ├── ConnectionKeeper.cs │ ├── Program.cs │ ├── Query.cs │ ├── Startup.cs │ └── TestDbContext.cs │ ├── LeanCode.IntegrationTestHelpers.Tests.csproj │ ├── TestApp.cs │ ├── TestConnectionStringTests.cs │ └── Tests.cs ├── Tools └── LeanCode.CodeAnalysis.Tests │ ├── Analyzers │ ├── CancellationTokensShouldFollowNamingConventionTests.cs │ ├── EnsureCQRSHandlersAreInProperNamespaceTests.cs │ ├── EnsureCQRSHandlersFollowNamingConventionTests.cs │ ├── EnsureCommandsQueriesAndOperationsHaveAuthorizersTests.cs │ ├── EnsureValidatorsFollowNamingConventionTests.cs │ └── SuggestCommandsHaveValidatorsTests.cs │ ├── CodeActions │ ├── AddAuthorizationAttributeCodeActionTests.cs │ ├── AddCommandValidatorCodeActionTests.cs │ ├── FixCQRSHandlerNamespaceTests.cs │ ├── FixCQRSHandlerNamingCodeActionTests.cs │ ├── FixCancellationTokenNamingCodeActionTests.cs │ └── FixCommandValidatorNamingCodeActionTests.cs │ ├── LeanCode.CodeAnalysis.Tests.csproj │ ├── TestSamples │ ├── Accepted │ │ ├── CQRS │ │ │ ├── Command_handlers.cs │ │ │ ├── Operation_handlers.cs │ │ │ ├── Query_handlers.cs │ │ │ └── Validators.cs │ │ ├── Contracts │ │ │ ├── Commands.cs │ │ │ ├── Operations.cs │ │ │ └── Queries.cs │ │ └── Methods.cs │ ├── Command_validation.cs │ └── Rejected │ │ ├── CQRS │ │ ├── Command_handlers.cs │ │ ├── Operation_handlers.cs │ │ ├── Query_handlers.cs │ │ └── Validators.cs │ │ ├── Contracts │ │ ├── Commands.cs │ │ ├── Operations.cs │ │ └── Queries.cs │ │ └── Methods.cs │ └── Verifiers │ ├── CodeFixVerifier.cs │ ├── DiagnosticResult.cs │ └── DiagnosticVerifier.cs └── coverage.xml /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "csharpier": { 6 | "version": "0.30.4", 7 | "commands": [ 8 | "dotnet-csharpier" 9 | ], 10 | "rollForward": false 11 | }, 12 | "dotnet-coverage": { 13 | "version": "17.13.1", 14 | "commands": [ 15 | "dotnet-coverage" 16 | ], 17 | "rollForward": false 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /.csharpierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "useTabs": false, 4 | "tabWidth": 4, 5 | "preprocessorSymbolSets": [] 6 | } 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | [Bb]in/ 2 | [Oo]bj/ 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.cs diff=csharp 3 | *.jpg binary 4 | *.png binary 5 | *.gif binary 6 | *.pdf diff=astextplain 7 | *.PDF diff=astextplain 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "nuget" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | microsoft: 9 | patterns: 10 | - "Microsoft.*" 11 | other: 12 | patterns: 13 | - "*" 14 | exclude-patterns: 15 | - "Microsoft.*" 16 | - package-ecosystem: "pip" 17 | directory: "/docs" 18 | schedule: 19 | interval: "weekly" 20 | groups: 21 | python-packages: 22 | patterns: 23 | - "*" 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: CoreLibrary Docs 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'docs/**' 7 | 8 | jobs: 9 | lint-changelog: 10 | name: Lint docs 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v2 15 | - name: Lint 16 | uses: DavidAnson/markdownlint-cli2-action@v13 17 | with: 18 | config: ./docs/.markdownlint.jsonc 19 | globs: 'docs/**/*.md' 20 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for MkDocs projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: "3.11" 9 | 10 | mkdocs: 11 | configuration: mkdocs.yml 12 | 13 | python: 14 | install: 15 | - requirements: docs/requirements.txt 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "ms-dotnettools.csharp", 5 | "csharpier.csharpier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".fake": true, 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "**/bin": true, 7 | "**/obj": true, 8 | "**/node_modules": true, 9 | "packages": true, 10 | "**/Logs": true, 11 | "paket-files": true, 12 | "**/obj/**/en": true, 13 | "**/obj/**/pl": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "dotnet", 4 | "args": [], 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "command": "dotnet", 10 | "args": [ 11 | "build" 12 | ], 13 | "problemMatcher": "$msCompile", 14 | "group": "build" 15 | }, 16 | { 17 | "label": "restore", 18 | "type": "shell", 19 | "command": "dotnet", 20 | "args": [ 21 | "restore" 22 | ], 23 | "isBackground": true, 24 | "problemMatcher": "$msCompile" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LeanCode 5 | LeanCode Core library 6 | 7 | preview 8 | library 9 | net9.0 10 | portable 11 | true 12 | enable 13 | latest 14 | AllEnabledByDefault 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | -------------------------------------------------------------------------------- /cloud-tests-infrastructure/.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc 35 | -------------------------------------------------------------------------------- /cloud-tests-infrastructure/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.2" 3 | 4 | backend "azurerm" {} 5 | 6 | required_providers { 7 | azuread = { 8 | source = "hashicorp/azuread" 9 | version = "~> 3.0" 10 | } 11 | azurerm = { 12 | source = "hashicorp/azurerm" 13 | version = "~> 4.12" 14 | } 15 | } 16 | } 17 | 18 | provider "azurerm" { 19 | tenant_id = var.tenant_id 20 | subscription_id = var.subscription_id 21 | 22 | features {} 23 | } 24 | 25 | provider "azuread" { 26 | tenant_id = var.tenant_id 27 | } 28 | 29 | resource "azurerm_resource_group" "rg" { 30 | name = var.resource_group_name 31 | location = var.location 32 | } 33 | 34 | resource "azuread_application" "tests" { 35 | display_name = "Core Library Tests" 36 | identifier_uris = ["https://corelibrary-tests.project.lncd.pl"] 37 | } 38 | 39 | resource "azuread_service_principal" "tests" { 40 | client_id = azuread_application.tests.client_id 41 | } 42 | 43 | resource "azuread_service_principal_password" "tests" { 44 | service_principal_id = azuread_service_principal.tests.id 45 | } 46 | -------------------------------------------------------------------------------- /cloud-tests-infrastructure/outputs.tf: -------------------------------------------------------------------------------- 1 | output "test_envs" { 2 | sensitive = true 3 | value = <<-EOT 4 | CORELIB_TESTS_TENANT_ID="${azuread_service_principal.tests.application_tenant_id}" 5 | CORELIB_TESTS_CLIENT_ID="${azuread_application.tests.client_id}" 6 | CORELIB_TESTS_CLIENT_SECRET="${azuread_service_principal_password.tests.value}" 7 | CORELIB_TESTS_NPGSQL_CONNECTION_STRING='${local.npg_connection_string}' 8 | CORELIB_TESTS_AZURE_BLOB_STORAGE_SERVICE_URI='${azurerm_storage_account.storage.primary_blob_endpoint}' 9 | CORELIB_TESTS_AZURE_TABLE_STORAGE_SERVICE_URI='${azurerm_storage_account.storage.primary_table_endpoint}' 10 | CORELIB_TESTS_AZURE_BLOB_STORAGE_CONTAINER_NAME='${local.blob_storage_container_name}' 11 | CORELIB_TESTS_AZURE_TABLE_STORAGE_TABLE_NAME='${local.table_storage_table_name}' 12 | EOT 13 | } 14 | -------------------------------------------------------------------------------- /cloud-tests-infrastructure/readme.md: -------------------------------------------------------------------------------- 1 | # Cloud tests infrastructure 2 | 3 | Cloud resources used in [LeanCode.Azure.Tests](../test/Infrastructure/LeanCode.Azure.Tests/LeanCode.Azure.Tests.csproj). 4 | 5 | All the necessary env variables are set in `test_credentials` output. 6 | -------------------------------------------------------------------------------- /cloud-tests-infrastructure/variables.tf: -------------------------------------------------------------------------------- 1 | variable "tenant_id" { 2 | type = string 3 | } 4 | 5 | variable "subscription_id" { 6 | type = string 7 | } 8 | 9 | variable "resource_group_name" { 10 | type = string 11 | } 12 | 13 | variable "location" { 14 | type = string 15 | } 16 | 17 | variable "postgres_server_name" { 18 | type = string 19 | } 20 | 21 | variable "storage_name" { 22 | type = string 23 | } 24 | -------------------------------------------------------------------------------- /docs/.markdownlint.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | // Line length 4 | "MD013": false, 5 | // Unordered list indentation 6 | "MD007": { 7 | "indent": 4 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docs/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "davidanson.vscode-markdownlint", 4 | "streetsidesoftware.code-spell-checker" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /docs/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "CQRS", 4 | "READMEs", 5 | "appsettings", 6 | "destructurer", 7 | "destructurers", 8 | "msbuild", 9 | "nuget", 10 | "nupkg", 11 | "typeof" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /docs/img/leancode_logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/docs/img/leancode_logo_black.png -------------------------------------------------------------------------------- /docs/img/leancode_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/docs/img/leancode_logo_white.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Babel==2.16.0 2 | certifi==2024.8.30 3 | charset-normalizer==3.4.0 4 | click==8.1.7 5 | colorama==0.4.6 6 | ghp-import==2.1.0 7 | idna==3.10 8 | Jinja2==3.1.4 9 | Markdown==3.7 10 | MarkupSafe==3.0.2 11 | mergedeep==1.3.4 12 | mkdocs==1.6.1 13 | mkdocs-material==9.5.47 14 | mkdocs-material-extensions==1.3.1 15 | packaging==24.2 16 | paginate==0.5.7 17 | pathspec==0.12.1 18 | platformdirs==4.3.6 19 | Pygments==2.18.0 20 | pymdown-extensions==10.12 21 | python-dateutil==2.9.0.post0 22 | PyYAML==6.0.2 23 | pyyaml_env_tag==0.1 24 | regex==2024.11.6 25 | requests==2.32.3 26 | six==1.16.0 27 | urllib3==2.2.3 28 | watchdog==6.0.0 29 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Primary color shades */ 3 | --md-primary-fg-color: #000000; 4 | /* Accent color shades */ 5 | --md-accent-fg-color: #c28f02; 6 | --md-typeset-a-color: #c28f02; 7 | } 8 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100", 4 | "allowPrerelease": false, 5 | "rollForward": "latestMinor" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Annotations/LeanCode.CQRS.Annotations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | 5 | 6 | 7 | 8 | %(Filename)%(Extension) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Annotations/LeanCode.Contracts.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/src/CQRS/LeanCode.CQRS.Annotations/LeanCode.Contracts.pb -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Annotations/PathAliasAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Annotations; 2 | 3 | /// 4 | /// Register command/query/operation under additional HTTP path. This could be useful for reorganizing contracts namespaces without introducing a breaking change. 5 | /// This attribute can be used multiple times. 6 | /// 7 | /// 8 | /// 9 | /// namespace LncdApp.Contracts; 10 | /// { 11 | /// [PathAlias("LncdApp.Contacts.Commands")] 12 | /// public class MyCommand : ICommand { } 13 | /// } 14 | /// 15 | ///
16 | /// MyCommand class will be registered as 'cqrs-base/command/LncdApp.Contracts.MyCommand' and 'cqrs-base/command/LncdApp.Contracts.Commands.MyCommand' 17 | ///
18 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] 19 | public sealed class PathAliasAttribute : Attribute 20 | { 21 | public string Path { get; } 22 | 23 | public PathAliasAttribute(string path) 24 | { 25 | Path = path; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Internals.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("LeanCode.CQRS.AspNetCore.Tests")] 4 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/LeanCode.CQRS.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/LocalCallLifetimeFeature.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http.Features; 2 | 3 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 4 | 5 | internal class LocalCallLifetimeFeature : IHttpRequestLifetimeFeature, IDisposable 6 | { 7 | private readonly CancellationTokenSource source; 8 | private CancellationToken requestAborted; 9 | 10 | public CancellationToken RequestAborted 11 | { 12 | get => requestAborted; 13 | set => requestAborted = value; 14 | } 15 | 16 | public CancellationToken CallAborted => source.Token; 17 | 18 | public LocalCallLifetimeFeature(CancellationToken cancellationToken) 19 | { 20 | source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 21 | requestAborted = source.Token; 22 | } 23 | 24 | public void Abort() => source.Cancel(); 25 | 26 | public void Dispose() => source.Dispose(); 27 | } 28 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/LocalCallServiceProvidersFeature.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http.Features; 2 | 3 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 4 | 5 | internal class LocalCallServiceProvidersFeature : IServiceProvidersFeature 6 | { 7 | public IServiceProvider RequestServices { get; set; } 8 | 9 | public LocalCallServiceProvidersFeature(IServiceProvider requestServices) 10 | { 11 | RequestServices = requestServices; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/NullEndpointFeature.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Http.Features; 3 | 4 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 5 | 6 | internal class NullEndpointFeature : IEndpointFeature 7 | { 8 | public static readonly NullEndpointFeature Empty = new(); 9 | 10 | public Endpoint? Endpoint 11 | { 12 | get => null; 13 | set { } 14 | } 15 | 16 | private NullEndpointFeature() { } 17 | } 18 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/NullRequestCookieCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Diagnostics.CodeAnalysis; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 6 | 7 | internal class NullRequestCookieCollection : IRequestCookieCollection 8 | { 9 | public static readonly NullRequestCookieCollection Empty = new(); 10 | 11 | public string? this[string key] => null; 12 | 13 | public int Count => 0; 14 | 15 | public ICollection Keys => []; 16 | 17 | private NullRequestCookieCollection() { } 18 | 19 | public bool ContainsKey(string key) => false; 20 | 21 | public IEnumerator> GetEnumerator() => 22 | Enumerable.Empty>().GetEnumerator(); 23 | 24 | public bool TryGetValue(string key, [NotNullWhen(true)] out string? value) 25 | { 26 | value = null; 27 | return false; 28 | } 29 | 30 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 31 | } 32 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/NullResponseCookies.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 4 | 5 | internal class NullResponseCookies : IResponseCookies 6 | { 7 | public static readonly NullResponseCookies Empty = new(); 8 | 9 | private NullResponseCookies() { } 10 | 11 | public void Append(string key, string value) { } 12 | 13 | public void Append(string key, string value, CookieOptions options) { } 14 | 15 | public void Delete(string key) { } 16 | 17 | public void Delete(string key, CookieOptions options) { } 18 | } 19 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/NullSession.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 5 | 6 | internal class NullSession : ISession 7 | { 8 | public static readonly NullSession Empty = new(); 9 | 10 | public bool IsAvailable => false; 11 | 12 | public string Id => ""; 13 | 14 | public IEnumerable Keys => []; 15 | 16 | private NullSession() { } 17 | 18 | public void Clear() { } 19 | 20 | public Task CommitAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; 21 | 22 | public Task LoadAsync(CancellationToken cancellationToken = default) => Task.CompletedTask; 23 | 24 | public void Remove(string key) { } 25 | 26 | public void Set(string key, byte[] value) { } 27 | 28 | public bool TryGetValue(string key, [NotNullWhen(true)] out byte[]? value) 29 | { 30 | value = null; 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/Context/NullWebSocketManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Net.WebSockets; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Local.Context; 6 | 7 | internal class NullWebSocketManager : WebSocketManager 8 | { 9 | public static readonly NullWebSocketManager Empty = new(); 10 | 11 | public override bool IsWebSocketRequest => false; 12 | 13 | public override IList WebSocketRequestedProtocols { get; } = new ReadOnlyCollection([]); 14 | 15 | private NullWebSocketManager() { } 16 | 17 | public override Task AcceptWebSocketAsync(string? subProtocol) => 18 | throw new NotSupportedException("WebSockets are not supported in local CQRS calls."); 19 | } 20 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/ILocalCommandExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.Contracts; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Local; 6 | 7 | public interface ILocalCommandExecutor 8 | { 9 | Task RunAsync(T command, ClaimsPrincipal user, CancellationToken cancellationToken = default) 10 | where T : ICommand; 11 | 12 | Task RunAsync( 13 | T command, 14 | ClaimsPrincipal user, 15 | IHeaderDictionary headers, 16 | CancellationToken cancellationToken = default 17 | ) 18 | where T : ICommand; 19 | } 20 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/ILocalOperationExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.Contracts; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Local; 6 | 7 | public interface ILocalOperationExecutor 8 | { 9 | Task ExecuteAsync( 10 | IOperation query, 11 | ClaimsPrincipal user, 12 | CancellationToken cancellationToken = default 13 | ); 14 | 15 | Task ExecuteAsync( 16 | IOperation query, 17 | ClaimsPrincipal user, 18 | IHeaderDictionary headers, 19 | CancellationToken cancellationToken = default 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/ILocalQueryExecutor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.Contracts; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Local; 6 | 7 | public interface ILocalQueryExecutor 8 | { 9 | Task GetAsync( 10 | IQuery query, 11 | ClaimsPrincipal user, 12 | CancellationToken cancellationToken = default 13 | ); 14 | 15 | Task GetAsync( 16 | IQuery query, 17 | ClaimsPrincipal user, 18 | IHeaderDictionary headers, 19 | CancellationToken cancellationToken = default 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/UnauthenticatedCQRSRequestException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.AspNetCore.Local; 2 | 3 | public class UnauthenticatedCQRSRequestException : Exception 4 | { 5 | public Type ObjectType { get; } 6 | 7 | public UnauthenticatedCQRSRequestException(Type objectType) 8 | : base($"The request {objectType.FullName} was not authenticated.") 9 | { 10 | ObjectType = objectType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/UnauthorizedCQRSRequestException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.AspNetCore.Local; 2 | 3 | public class UnauthorizedCQRSRequestException : Exception 4 | { 5 | public Type ObjectType { get; } 6 | 7 | public UnauthorizedCQRSRequestException(Type objectType) 8 | : base($"The request {objectType.FullName} was not authorized.") 9 | { 10 | ObjectType = objectType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Local/UnknownStatusCodeException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.AspNetCore.Local; 2 | 3 | public class UnknownStatusCodeException : Exception 4 | { 5 | public int StatusCode { get; } 6 | public Type ObjectType { get; } 7 | 8 | public UnknownStatusCodeException(int statusCode, Type objectType) 9 | : base($"Unknown status code {statusCode} for request {objectType.FullName}.") 10 | { 11 | StatusCode = statusCode; 12 | ObjectType = objectType; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Middleware/CQRSPipelineFinalizer.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | using LeanCode.OpenTelemetry; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Middleware; 6 | 7 | internal static class CQRSPipelineFinalizer 8 | { 9 | public static async Task HandleAsync(HttpContext context) 10 | { 11 | var metadata = context.GetCQRSObjectMetadata(); 12 | var payload = context.GetCQRSRequestPayload(); 13 | 14 | using var activity = LeanCodeActivitySource.StartMiddleware("Execution"); 15 | 16 | var result = await metadata.ObjectExecutor(context, payload); 17 | 18 | payload.SetResult(ExecutionResult.WithPayload(result)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Middleware/NonProductionResponseLoggerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | using LeanCode.Logging; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace LeanCode.CQRS.AspNetCore.Middleware; 7 | 8 | public class NonProductionResponseLoggerMiddleware 9 | { 10 | private readonly IHostEnvironment hostEnvironment; 11 | private readonly ILogger logger; 12 | 13 | public NonProductionResponseLoggerMiddleware( 14 | IHostEnvironment hostEnvironment, 15 | ILogger logger 16 | ) 17 | { 18 | this.hostEnvironment = hostEnvironment; 19 | this.logger = logger; 20 | } 21 | 22 | public async Task InvokeAsync(HttpContext httpContext, RequestDelegate next) 23 | { 24 | await next(httpContext); 25 | 26 | var payload = httpContext.GetCQRSRequestPayload(); 27 | 28 | if (!hostEnvironment.IsProduction()) 29 | { 30 | logger.Information("Request executed with response {@Response}", payload.Result); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Middleware/ResponseLoggerMiddleware.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | using LeanCode.Logging; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Middleware; 6 | 7 | public class ResponseLoggerMiddleware 8 | { 9 | private readonly RequestDelegate next; 10 | private readonly ILogger logger; 11 | 12 | public ResponseLoggerMiddleware(RequestDelegate next, ILogger logger) 13 | { 14 | this.next = next; 15 | this.logger = logger; 16 | } 17 | 18 | public async Task InvokeAsync(HttpContext httpContext) 19 | { 20 | await next(httpContext); 21 | var result = httpContext.GetCQRSRequestPayload().Result; 22 | logger.Information("Request executed with response {@Response}", result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ApiDescriptionTags.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | using Microsoft.AspNetCore.Routing; 3 | 4 | namespace LeanCode.CQRS.AspNetCore.Registration; 5 | 6 | public static class ApiDescriptionTags 7 | { 8 | public static IReadOnlyList FullNamespace(RouteEndpoint _, CQRSObjectMetadata m) 9 | { 10 | return [(m.ObjectType.Namespace ?? "").Replace(".", " - ", StringComparison.InvariantCulture)]; 11 | } 12 | 13 | public static Func> SkipNamespacePrefix(int skipFirst) 14 | { 15 | return (_, m) => 16 | { 17 | var parts = (m.ObjectType.Namespace ?? "").Split('.').Skip(skipFirst); 18 | return [string.Join(" - ", parts)]; 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Registration/CQRSApiDescriptionConfiguration.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | using Microsoft.AspNetCore.Routing; 3 | 4 | namespace LeanCode.CQRS.AspNetCore.Registration; 5 | 6 | public class CQRSApiDescriptionConfiguration 7 | { 8 | public Func> TagsMapping { get; init; } = 9 | ApiDescriptionTags.FullNamespace; 10 | 11 | public Func SummaryMapping { get; init; } = 12 | static (_, m) => m.ObjectType.FullName ?? ""; 13 | 14 | public Func DescriptionMapping { get; init; } = 15 | static (_, m) => $"Executed by {m.HandlerType.FullName ?? "unknown handler"}"; 16 | } 17 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Registration/EndpointMetadata.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http.Metadata; 2 | 3 | namespace LeanCode.CQRS.AspNetCore.Registration; 4 | 5 | internal class EndpointTags(IReadOnlyList tags) : ITagsMetadata 6 | { 7 | public IReadOnlyList Tags => tags; 8 | } 9 | 10 | internal class EndpointSummary(string summary) : IEndpointSummaryMetadata 11 | { 12 | public string Summary => summary; 13 | } 14 | 15 | internal class EndpointDescription(string description) : IEndpointDescriptionMetadata 16 | { 17 | public string Description => description; 18 | } 19 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ICQRSObjectSource.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Execution; 2 | 3 | namespace LeanCode.CQRS.AspNetCore.Registration; 4 | 5 | public interface ICQRSObjectSource 6 | { 7 | CQRSObjectMetadata MetadataFor(Type type); 8 | } 9 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/CQRSRequestPayload.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | public class CQRSRequestPayload 4 | { 5 | public object Payload { get; } 6 | 7 | public ExecutionResult? Result { get; private set; } 8 | 9 | public CQRSRequestPayload(object payload) 10 | { 11 | Payload = payload; 12 | } 13 | 14 | public void SetResult(ExecutionResult? result) 15 | { 16 | if (Result is not null) 17 | { 18 | throw new InvalidOperationException("The request has been already set."); 19 | } 20 | 21 | Result = result; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/CommandExecutionInvalidException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | /// 4 | /// Use to propagate arbitrary from the command handler. 5 | /// Use as a last resort - when validation and execution are so coupled that separating it is too complicated. 6 | /// 7 | public class CommandExecutionInvalidException : Exception 8 | { 9 | public int ErrorCode { get; private set; } 10 | 11 | public CommandExecutionInvalidException(int errorCode, string errorMessage, Exception? innerException = null) 12 | : base(errorMessage, innerException) 13 | { 14 | ErrorCode = errorCode; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/CommandHandlerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | public class CommandHandlerNotFoundException : Exception 4 | { 5 | public Type CommandType { get; } 6 | 7 | public CommandHandlerNotFoundException(Type commandType) 8 | : base($"Cannot find handler for command {commandType.Name}.") 9 | { 10 | CommandType = commandType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/ExecutionResult.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | public readonly record struct ExecutionResult 4 | { 5 | public int StatusCode { get; private init; } 6 | 7 | /// 8 | /// Indicates that result should be written in http response, however may still be a null value. 9 | /// 10 | public bool HasPayload { get; private init; } 11 | public object? Payload { get; private init; } 12 | 13 | public static ExecutionResult Empty(int code) => new() { StatusCode = code }; 14 | 15 | public static ExecutionResult WithPayload(object? payload, int code = 200) => 16 | new() 17 | { 18 | StatusCode = code, 19 | HasPayload = true, 20 | Payload = payload, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace LeanCode.CQRS.Execution; 5 | 6 | public interface ICommandHandler 7 | where TCommand : ICommand 8 | { 9 | Task ExecuteAsync(HttpContext context, TCommand command); 10 | } 11 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/IOperationHandler.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace LeanCode.CQRS.Execution; 5 | 6 | public interface IOperationHandler 7 | where TOperation : IOperation 8 | { 9 | Task ExecuteAsync(HttpContext context, TOperation operation); 10 | } 11 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/IQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace LeanCode.CQRS.Execution; 5 | 6 | public interface IQueryHandler 7 | where TQuery : IQuery 8 | { 9 | Task ExecuteAsync(HttpContext context, TQuery query); 10 | } 11 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/LeanCode.CQRS.Execution.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/OperationHandlerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | public class OperationHandlerNotFoundException : Exception 4 | { 5 | public OperationHandlerNotFoundException(Type operationType) 6 | : base($"Cannot find handler for operation {operationType.Name}.") 7 | { 8 | OperationType = operationType; 9 | } 10 | 11 | public Type OperationType { get; } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Execution/QueryHandlerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Execution; 2 | 3 | public class QueryHandlerNotFoundException : Exception 4 | { 5 | public Type QueryType { get; } 6 | 7 | public QueryHandlerNotFoundException(Type queryType) 8 | : base($"Cannot find handler for query {queryType.Name}.") 9 | { 10 | QueryType = queryType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/AsyncEventsInterceptorExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | 3 | namespace LeanCode.CQRS.MassTransitRelay; 4 | 5 | public static class AsyncEventsInterceptorExtensions 6 | { 7 | public static async Task<(TResult Result, List Events)> CaptureEventsOfAsync( 8 | this AsyncEventsInterceptor interceptor, 9 | Func> action 10 | ) 11 | { 12 | interceptor.Prepare(); 13 | var result = await action(); 14 | var interceptedEvents = interceptor.CaptureQueue()?.ToList() ?? new List(); 15 | 16 | return (result, interceptedEvents); 17 | } 18 | 19 | public static async Task> CaptureEventsOfAsync( 20 | this AsyncEventsInterceptor interceptor, 21 | Func action 22 | ) 23 | { 24 | interceptor.Prepare(); 25 | await action(); 26 | var interceptedEvents = interceptor.CaptureQueue()?.ToList() ?? new List(); 27 | 28 | return interceptedEvents; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/BusObserverExtensions.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace LeanCode.CQRS.MassTransitRelay; 5 | 6 | public static class BusObserverExtensions 7 | { 8 | public static void ConnectBusObservers(this IBusObserverConnector bus, IBusRegistrationContext context) 9 | { 10 | var observers = context.GetServices(); 11 | foreach (var obs in observers) 12 | { 13 | bus.ConnectBusObserver(obs); 14 | } 15 | } 16 | 17 | public static void ConnectReceiveEndpointObservers( 18 | this IReceiveEndpointObserverConnector rcv, 19 | IBusRegistrationContext context 20 | ) 21 | { 22 | var recvObservers = context.GetServices(); 23 | foreach (var obs in recvObservers) 24 | { 25 | rcv.ConnectReceiveEndpointObserver(obs); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/LeanCode.CQRS.MassTransitRelay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/MassTransitRelayApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.MassTransitRelay.Middleware; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace LeanCode.CQRS.MassTransitRelay; 6 | 7 | public static class MassTransitRelayApplicationBuilderExtensions 8 | { 9 | public static IApplicationBuilder CommitTransaction(this IApplicationBuilder builder) 10 | where TDbContext : DbContext 11 | { 12 | builder.UseMiddleware>(); 13 | return builder; 14 | } 15 | 16 | public static IApplicationBuilder PublishEvents(this IApplicationBuilder builder) 17 | { 18 | builder.UseMiddleware(); 19 | return builder; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/Middleware/CommitDatabaseTransactionMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.CQRS.MassTransitRelay.Middleware; 5 | 6 | public class CommitDatabaseTransactionMiddleware 7 | where TDbContext : DbContext 8 | { 9 | private readonly RequestDelegate next; 10 | 11 | public CommitDatabaseTransactionMiddleware(RequestDelegate next) 12 | { 13 | this.next = next; 14 | } 15 | 16 | public async Task InvokeAsync(HttpContext httpContext, TDbContext dbContext) 17 | { 18 | await next(httpContext); 19 | await dbContext.SaveChangesAsync(httpContext.RequestAborted); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.MassTransitRelay/Testing/ResettableBusActivityMonitorInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace LeanCode.CQRS.MassTransitRelay.Testing; 4 | 5 | internal sealed class ResettableBusActivityMonitorInitializer : IHostedService 6 | { 7 | public ResettableBusActivityMonitorInitializer(ResettableBusActivityMonitor monitor) 8 | { 9 | // ServiceCollection does not allow any auto-initialize of services, 10 | // so we just want to instantiate it somehow, to start observing the bus 11 | _ = monitor; 12 | } 13 | 14 | public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; 15 | 16 | public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; 17 | } 18 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.RemoteHttp.Client/HttpResponseMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using static System.Net.HttpStatusCode; 2 | 3 | namespace LeanCode.CQRS.RemoteHttp.Client; 4 | 5 | public static class HttpResponseMessageExtensions 6 | { 7 | public static void HandleCommonCQRSErrors(this HttpResponseMessage response) 8 | where TNotFound : Exception, new() 9 | where TBadRequest : Exception, new() 10 | { 11 | switch (response.StatusCode) 12 | { 13 | case NotFound: 14 | throw new TNotFound(); 15 | case BadRequest: 16 | throw new TBadRequest(); 17 | case InternalServerError: 18 | throw new InternalServerErrorException(); 19 | case Unauthorized: 20 | throw new UnauthorizedException(); 21 | case Forbidden: 22 | throw new ForbiddenException(); 23 | } 24 | 25 | if (!response.IsSuccessStatusCode) 26 | { 27 | throw new HttpCallErrorException(response.StatusCode); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.RemoteHttp.Client/LeanCode.CQRS.RemoteHttp.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.ScopedTransaction/LeanCode.CQRS.ScopedTransaction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.ScopedTransaction/PessimisticConcurrencyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.CQRS.ScopedTransaction; 5 | 6 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 7 | public sealed class PessimisticConcurrencyAttribute : Attribute 8 | { 9 | /// 10 | /// Whether should explicitly call . 11 | /// 12 | public bool SaveChanges { get; } 13 | public IsolationLevel IsolationLevel { get; } 14 | 15 | public PessimisticConcurrencyAttribute( 16 | IsolationLevel isolationLevel = IsolationLevel.ReadCommitted, 17 | bool saveChanges = true 18 | ) 19 | { 20 | SaveChanges = saveChanges; 21 | IsolationLevel = isolationLevel; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/ClaimsPrincipalExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace LeanCode.CQRS.Security; 4 | 5 | public static class ClaimsPrincipalExtensions 6 | { 7 | public static bool HasPermission( 8 | this ClaimsPrincipal claimsPrincipal, 9 | RoleRegistry registry, 10 | params string[] permissions 11 | ) 12 | { 13 | return registry.All.Any(role => 14 | claimsPrincipal.IsInRole(role.Name) && permissions.Any(role.Permissions.Contains) 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/CustomAuthorizerNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Security; 2 | 3 | public class CustomAuthorizerNotFoundException : Exception 4 | { 5 | public Type AuthorizerType { get; } 6 | 7 | public CustomAuthorizerNotFoundException(Type authorizerType) 8 | : base($"Cannot find custom authorizer for {authorizerType.Name}.") 9 | { 10 | AuthorizerType = authorizerType; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/Exceptions/InsufficientPermissionException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Security.Exceptions; 2 | 3 | public class InsufficientPermissionException : Exception 4 | { 5 | public string? AuthorizerName { get; private set; } 6 | 7 | public InsufficientPermissionException(string message, string? authorizerName) 8 | : base(message) 9 | { 10 | AuthorizerName = authorizerName; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/Exceptions/UnauthenticatedException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Security.Exceptions; 2 | 3 | public class UnauthenticatedException : Exception 4 | { 5 | public UnauthenticatedException(string message) 6 | : base(message) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/IAuthorizerResolver.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Security; 2 | 3 | public interface IAuthorizerResolver 4 | { 5 | ICustomAuthorizerWrapper? FindAuthorizer(Type authorizerType, Type objectType); 6 | } 7 | 8 | public interface ICustomAuthorizerWrapper 9 | { 10 | Type UnderlyingAuthorizer { get; } 11 | Task CheckIfAuthorizedAsync(object appContext, object obj, object? customData); 12 | } 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/IRoleRegistration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Security; 2 | 3 | public interface IRoleRegistration 4 | { 5 | IEnumerable Roles { get; } 6 | } 7 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/LeanCode.CQRS.Security.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/Role.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace LeanCode.CQRS.Security; 4 | 5 | public class Role 6 | { 7 | public string Name { get; } 8 | public ImmutableHashSet Permissions { get; } 9 | 10 | public Role(string name, params string[] permissions) 11 | { 12 | Validate(name); 13 | 14 | Name = name; 15 | Permissions = ImmutableHashSet.Create(permissions); 16 | } 17 | 18 | public Role(string name, IEnumerable permissions) 19 | { 20 | Validate(name); 21 | 22 | Name = name; 23 | Permissions = permissions.ToImmutableHashSet(); 24 | } 25 | 26 | private static void Validate(string name) 27 | { 28 | if (string.IsNullOrWhiteSpace(name)) 29 | { 30 | throw new ArgumentException("Name must be specified.", nameof(name)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Security/RoleRegistry.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace LeanCode.CQRS.Security; 4 | 5 | public sealed class RoleRegistry 6 | { 7 | public ImmutableList All { get; } 8 | 9 | public RoleRegistry(IEnumerable registrations) 10 | { 11 | All = registrations.SelectMany(r => r.Roles).ToImmutableList(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation.Fluent/FluentValidationErrorState.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.Validation.Fluent; 2 | 3 | public sealed class FluentValidatorErrorState 4 | { 5 | public int ErrorCode { get; } 6 | 7 | public FluentValidatorErrorState(int code) 8 | { 9 | ErrorCode = code; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation.Fluent/FluentValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using FluentValidation.Results; 3 | 4 | namespace LeanCode.CQRS.Validation.Fluent; 5 | 6 | public static class FluentValidatorExtensions 7 | { 8 | public static IRuleBuilderOptions WithCode( 9 | this IRuleBuilderOptions rule, 10 | int code 11 | ) 12 | { 13 | return rule.WithState(_ => new FluentValidatorErrorState(code)); 14 | } 15 | 16 | public static void AddValidationError( 17 | this ValidationContext ctx, 18 | string errorMessage, 19 | int errorCode, 20 | string? propertyName = null 21 | ) 22 | { 23 | errorMessage = ctx.MessageFormatter.BuildMessage(errorMessage); 24 | ctx.AddFailure( 25 | new ValidationFailure(propertyName ?? ctx.PropertyPath, errorMessage) 26 | { 27 | CustomState = new FluentValidatorErrorState(errorCode), 28 | } 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation.Fluent/LeanCode.CQRS.Validation.Fluent.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation.Fluent/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using LeanCode.Components; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace LeanCode.CQRS.Validation.Fluent; 6 | 7 | public static class ServiceProviderExtensions 8 | { 9 | public static IServiceCollection AddFluentValidation( 10 | this IServiceCollection serviceCollection, 11 | TypesCatalog catalog, 12 | ServiceLifetime validatorsLifetime = ServiceLifetime.Scoped 13 | ) 14 | { 15 | serviceCollection.AddScoped(typeof(ICommandValidator<>), typeof(AdapterLoader<>)); 16 | serviceCollection.RegisterGenericTypes(catalog, typeof(IValidator<>), validatorsLifetime); 17 | 18 | return serviceCollection; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation.Fluent/ValidationContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace LeanCode.CQRS.Validation.Fluent; 6 | 7 | public static class ValidationContextExtensions 8 | { 9 | public const string HttpContextKey = "HttpContext"; 10 | 11 | public static HttpContext HttpContext(this IValidationContext ctx) 12 | { 13 | if (ctx.RootContextData.TryGetValue(HttpContextKey, out var httpContext)) 14 | { 15 | return (HttpContext)httpContext; 16 | } 17 | else 18 | { 19 | throw new InvalidOperationException( 20 | "`HttpContext` is not available in validation context. Ensure you are calling the validator through FluentValidationCommandValidatorAdapter." 21 | ); 22 | } 23 | } 24 | 25 | public static T GetService(this IValidationContext ctx) 26 | where T : notnull 27 | { 28 | return ctx.HttpContext().RequestServices.GetRequiredService(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation/ICommandValidator.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.Contracts.Validation; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.Validation; 6 | 7 | public interface ICommandValidator 8 | where TCommand : ICommand 9 | { 10 | Task ValidateAsync(HttpContext httpContext, TCommand command); 11 | } 12 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation/ICommandValidatorResolver.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.Contracts.Validation; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.Validation; 6 | 7 | public interface ICommandValidatorResolver 8 | { 9 | ICommandValidatorWrapper? FindCommandValidator(Type commandType); 10 | } 11 | 12 | /// 13 | /// Marker interface, do not use directly. 14 | /// 15 | public interface ICommandValidatorWrapper 16 | { 17 | Task ValidateAsync(HttpContext appContext, ICommand command); 18 | } 19 | -------------------------------------------------------------------------------- /src/CQRS/LeanCode.CQRS.Validation/LeanCode.CQRS.Validation.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Core/LeanCode.Components.Autofac/IAppModule.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Core; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace LeanCode.Components.Autofac; 6 | 7 | public interface IAppModule : IModule 8 | { 9 | void ConfigureServices(IServiceCollection services); 10 | } 11 | 12 | public class AppModule : Module, IAppModule 13 | { 14 | public virtual void ConfigureServices(IServiceCollection services) { } 15 | } 16 | -------------------------------------------------------------------------------- /src/Core/LeanCode.Components.Autofac/LeanCode.Components.Autofac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Core/LeanCode.Components/LeanCode.Components.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Core/LeanCode.Startup.MicrosoftDI/LeanCode.Startup.MicrosoftDI.csproj: -------------------------------------------------------------------------------- 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/Core/LeanCode.Startup.MicrosoftDI/LeanProgram.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace LeanCode.Startup.MicrosoftDI; 6 | 7 | public static class LeanProgram 8 | { 9 | public const string SystemLoggersEntryName = "Serilog:SystemLoggers"; 10 | 11 | public static IHostBuilder BuildMinimalHost() 12 | where TStartup : class 13 | { 14 | return new HostBuilder() 15 | .ConfigureAppConfiguration( 16 | (_, config) => 17 | { 18 | config.AddEnvironmentVariables(); 19 | } 20 | ) 21 | .ConfigureWebHost(builder => 22 | { 23 | builder 24 | .UseKestrel( 25 | (builderContext, options) => 26 | { 27 | options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true); 28 | } 29 | ) 30 | .UseStartup(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Core/LeanCode.Startup.MicrosoftDI/LeanStartup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Hosting; 5 | using Serilog; 6 | 7 | namespace LeanCode.Startup.MicrosoftDI; 8 | 9 | public abstract class LeanStartup 10 | { 11 | protected IConfiguration Configuration { get; } 12 | 13 | protected virtual bool CloseAndFlushLogger { get; } = true; 14 | 15 | protected LeanStartup(IConfiguration config) 16 | { 17 | Configuration = config; 18 | } 19 | 20 | public abstract void ConfigureServices(IServiceCollection services); 21 | 22 | public void Configure(IApplicationBuilder app) 23 | { 24 | ConfigureApp(app); 25 | 26 | if (CloseAndFlushLogger) 27 | { 28 | var appLifetime = app.ApplicationServices.GetRequiredService(); 29 | appLifetime.ApplicationStopped.Register(Log.CloseAndFlush); 30 | } 31 | } 32 | 33 | protected abstract void ConfigureApp(IApplicationBuilder app); 34 | } 35 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.EF/DbContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.DomainModels.EF; 5 | 6 | public static class DbContextExtensions 7 | { 8 | public static void SoftDeleteItems(this DbContext dbContext) 9 | { 10 | var softDeletables = dbContext 11 | .ChangeTracker.Entries() 12 | .Where(p => p.State == EntityState.Deleted && p.Entity is ISoftDeletable); 13 | 14 | foreach (var entry in softDeletables) 15 | { 16 | entry.State = EntityState.Modified; 17 | entry.CurrentValues[nameof(ISoftDeletable.IsDeleted)] = true; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.EF/LeanCode.DomainModels.EF.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.EF/QueryExpressionVisitorInterceptor.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using Microsoft.EntityFrameworkCore.Diagnostics; 3 | 4 | namespace LeanCode.DomainModels.EF; 5 | 6 | public sealed class QueryExpressionVisitorInterceptor(ExpressionVisitor visitor) : IQueryExpressionInterceptor 7 | { 8 | public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData) => 9 | visitor.Visit(queryExpression); 10 | } 11 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.EF/SimpleEFRepository.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.DomainModels.EF; 5 | 6 | public sealed class SimpleEFRepository : EFRepository 7 | where TEntity : class, IAggregateRoot 8 | where TIdentity : IEquatable 9 | where TContext : DbContext 10 | { 11 | public SimpleEFRepository(TContext dbContext) 12 | : base(dbContext) { } 13 | 14 | public override Task FindAsync(TIdentity id, CancellationToken cancellationToken = default) => 15 | DbSet.AsTracking().FirstOrDefaultAsync(e => e.Id.Equals(id), cancellationToken); 16 | } 17 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.Generators/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | # Release 1.0 2 | 3 | ## New Rules 4 | 5 | Rule ID -| Category | Severity | Notes 6 | ---------|----------|----------|-------------------- 7 | LNCD0005 | Domain | Error | TypedIdGenerator 8 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.Generators/LeanCode.DomainModels.Generators.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.Generators/TypedIdData.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace LeanCode.DomainModels.Generators; 4 | 5 | public sealed class TypedIdData 6 | { 7 | public TypedIdFormat Format { get; } 8 | public string Namespace { get; } 9 | public string TypeName { get; } 10 | public string? CustomPrefix { get; } 11 | public bool SkipRandomGenerator { get; } 12 | public bool IsValid { get; } 13 | public Location? Location { get; } 14 | 15 | public TypedIdData( 16 | TypedIdFormat format, 17 | string @namespace, 18 | string typeName, 19 | string? customSlug, 20 | bool skipRandomGenerator, 21 | bool isValid, 22 | Location? location 23 | ) 24 | { 25 | Format = format; 26 | Namespace = @namespace; 27 | TypeName = typeName; 28 | CustomPrefix = customSlug; 29 | SkipRandomGenerator = skipRandomGenerator; 30 | IsValid = isValid; 31 | Location = location; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels.Generators/TypedIdFormat.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Generators; 2 | 3 | public enum TypedIdFormat 4 | { 5 | RawInt = 0, 6 | RawLong = 1, 7 | RawGuid = 2, 8 | PrefixedGuid = 3, 9 | PrefixedUlid = 4, 10 | } 11 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/DataAccess/IRepository.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | 3 | namespace LeanCode.DomainModels.DataAccess; 4 | 5 | public interface IRepository 6 | where TEntity : class, IAggregateRootWithoutOptimisticConcurrency 7 | where TIdentity : notnull 8 | { 9 | Task FindAsync(TIdentity id, CancellationToken cancellationToken = default); 10 | 11 | void Add(TEntity entity); 12 | void Delete(TEntity entity); 13 | void DeleteRange(IEnumerable entities); 14 | void Update(TEntity entity); 15 | } 16 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/LeanCode.DomainModels.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/DomainEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace LeanCode.DomainModels.Model; 4 | 5 | public static class DomainEvents 6 | { 7 | public static IDomainEventInterceptor? EventInterceptor { get; private set; } 8 | 9 | public static void SetInterceptor(IDomainEventInterceptor interceptor) 10 | { 11 | EventInterceptor = interceptor; 12 | } 13 | 14 | [SuppressMessage("?", "CA1030", Justification = "Convention for `DomainEvents`.")] 15 | public static void Raise(TEvent domainEvent) 16 | where TEvent : class, IDomainEvent 17 | { 18 | EventInterceptor?.Intercept(domainEvent); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/IAggregateRoot.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface IAggregateRoot 4 | : IAggregateRootWithoutOptimisticConcurrency, 5 | IOptimisticConcurrency 6 | where TIdentity : notnull { } 7 | 8 | public interface IAggregateRootWithoutOptimisticConcurrency 9 | : IEntity, 10 | IAggregateRootWithoutOptimisticConcurrency 11 | where TIdentity : notnull { } 12 | 13 | [System.Diagnostics.CodeAnalysis.SuppressMessage("?", "CA1040", Justification = "Marker interface.")] 14 | public interface IAggregateRootWithoutOptimisticConcurrency { } 15 | 16 | [Obsolete("`IIdentifiable` is obsolete. Use `IEntity` instead.")] 17 | public interface IIdentifiable : IEntity 18 | where TIdentity : notnull { } 19 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/IDomainEvent.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface IDomainEvent 4 | { 5 | Guid Id { get; } 6 | DateTime DateOccurred { get; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/IDomainEventInterceptor.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface IDomainEventInterceptor 4 | { 5 | void Intercept(IDomainEvent domainEvent); 6 | } 7 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface IEntity 4 | where TIdentity : notnull 5 | { 6 | TIdentity Id { get; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/IOptimisticConcurrency.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface IOptimisticConcurrency 4 | { 5 | DateTime DateModified { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/ISoftDeletable.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public interface ISoftDeletable 4 | { 5 | bool IsDeleted { get; } 6 | } 7 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.DomainModels/Model/ValueObject.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.DomainModels.Model; 2 | 3 | public abstract record ValueObject; 4 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.TimeProvider/LeanCode.TimeProvider.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.TimeProvider/Time.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.TimeProvider; 2 | 3 | public static class Time 4 | { 5 | public static System.TimeProvider Provider { get; set; } = UtcSystemTimeProvider.Instance; 6 | 7 | public static DateTime UtcNow => NowWithOffset.UtcDateTime; 8 | public static DateTime Now => NowWithOffset.DateTime; 9 | public static DateTimeOffset NowWithOffset => Provider.GetLocalNow(); 10 | } 11 | -------------------------------------------------------------------------------- /src/Domain/LeanCode.TimeProvider/UtcSystemTimeProvider.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.TimeProvider; 2 | 3 | public sealed class UtcSystemTimeProvider : System.TimeProvider 4 | { 5 | public static UtcSystemTimeProvider Instance { get; } = new(); 6 | 7 | public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.Utc; 8 | } 9 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UrlHelper/LeanCode.UrlHelper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UrlHelper/UrlHelper.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode; 2 | 3 | public static class UrlHelper 4 | { 5 | public static string Concat(string a, string b) 6 | { 7 | return a.TrimEnd('/') + '/' + b.TrimStart('/'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UserIdExtractors/Extractors/GuidUserIdExtractor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace LeanCode.UserIdExtractors.Extractors; 4 | 5 | public sealed class GuidUserIdExtractor : IUserIdExtractor 6 | { 7 | private readonly string userIdClaim; 8 | 9 | public GuidUserIdExtractor(string userIdClaim) 10 | { 11 | this.userIdClaim = userIdClaim; 12 | } 13 | 14 | public Guid Extract(ClaimsPrincipal user) 15 | { 16 | var claim = user.FindFirst(userIdClaim)?.Value; 17 | ArgumentException.ThrowIfNullOrEmpty(claim); 18 | 19 | return Guid.Parse(claim); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UserIdExtractors/Extractors/PrefixedTypedUserIdExtractor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.DomainModels.Ids; 3 | 4 | namespace LeanCode.UserIdExtractors.Extractors; 5 | 6 | public sealed class PrefixedTypedUserIdExtractor : IUserIdExtractor 7 | where TId : struct, IPrefixedTypedId 8 | { 9 | private readonly string userIdClaim; 10 | 11 | public PrefixedTypedUserIdExtractor(string userIdClaim) 12 | { 13 | this.userIdClaim = userIdClaim; 14 | } 15 | 16 | public TId Extract(ClaimsPrincipal user) 17 | { 18 | var claim = user.FindFirst(userIdClaim)?.Value; 19 | ArgumentException.ThrowIfNullOrEmpty(claim); 20 | 21 | return TId.Parse(claim); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UserIdExtractors/Extractors/StringUserIdExtractor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace LeanCode.UserIdExtractors.Extractors; 4 | 5 | public class StringUserIdExtractor : IUserIdExtractor 6 | { 7 | private readonly string userIdClaim; 8 | 9 | public StringUserIdExtractor(string userIdClaim) 10 | { 11 | this.userIdClaim = userIdClaim; 12 | } 13 | 14 | public string Extract(ClaimsPrincipal user) 15 | { 16 | var claim = user.FindFirst(userIdClaim)?.Value; 17 | ArgumentException.ThrowIfNullOrEmpty(claim); 18 | 19 | return claim; 20 | } 21 | } 22 | 23 | public sealed class GenericStringUserIdExtractor : StringUserIdExtractor, IUserIdExtractor 24 | { 25 | public GenericStringUserIdExtractor(string userIdClaim) 26 | : base(userIdClaim) { } 27 | } 28 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UserIdExtractors/IUserIdExtractor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | 3 | namespace LeanCode.UserIdExtractors; 4 | 5 | public interface IUserIdExtractor : IUserIdExtractor 6 | where TUserId : IEquatable 7 | { 8 | new TUserId Extract(ClaimsPrincipal user); 9 | string IUserIdExtractor.Extract(ClaimsPrincipal user) => Extract(user).ToString()!; 10 | } 11 | 12 | public interface IUserIdExtractor 13 | { 14 | string Extract(ClaimsPrincipal user); 15 | } 16 | -------------------------------------------------------------------------------- /src/Helpers/LeanCode.UserIdExtractors/LeanCode.UserIdExtractors.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AI.Contracts/LeanCode.AI.Contracts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AI.Contracts/McpToolAttribute.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.AI.Contracts; 4 | 5 | [ExcludeFromContractsGeneration] 6 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] 7 | public sealed class McpToolAttribute(string description) : Attribute 8 | { 9 | public string Description { get; } = description; 10 | public string? Name { get; init; } 11 | public string? Title { get; init; } 12 | public McpToolHints Hints { get; init; } = McpToolHints.Default; 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AI.Contracts/McpToolHints.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.AI.Contracts; 4 | 5 | [ExcludeFromContractsGeneration] 6 | [Flags] 7 | public enum McpToolHints 8 | { 9 | None = 0, 10 | Default = 1, 11 | 12 | Destructive = 0b10, 13 | Idempotent = 0b100, 14 | OpenWorld = 0b1000, 15 | ReadOnly = 0b10000, 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AI.McpServer/LeanCode.AI.McpServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/AppicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.AuditLogs; 5 | 6 | public static class ApplicationBuilderExtensions 7 | { 8 | public static IApplicationBuilder Audit(this IApplicationBuilder builder) 9 | where TDbContext : DbContext 10 | { 11 | builder.UseMiddleware>(); 12 | return builder; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/AuditLogsConsumer.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | 3 | namespace LeanCode.AuditLogs; 4 | 5 | public class AuditLogsConsumer : IConsumer 6 | { 7 | private readonly IAuditLogStorage auditLogStorage; 8 | 9 | public AuditLogsConsumer(IAuditLogStorage auditLogStorage) 10 | { 11 | this.auditLogStorage = auditLogStorage; 12 | } 13 | 14 | public Task Consume(ConsumeContext context) 15 | { 16 | return auditLogStorage.StoreEventAsync(context.Message, context.CancellationToken); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/AuditLogsExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.AuditLogs; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | public static class AuditLogsExtensions 6 | { 7 | public static IServiceCollection AddAzureStorageAuditLogs( 8 | this IServiceCollection services, 9 | AzureBlobAuditLogStorageConfiguration config 10 | ) 11 | { 12 | services.AddSingleton(config); 13 | services.AddTransient(); 14 | services.AddTransient(); 15 | return services; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/AuditLogsMiddleware.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace LeanCode.AuditLogs; 6 | 7 | public class AuditLogsMiddleware 8 | where TDbContext : DbContext 9 | { 10 | private readonly RequestDelegate next; 11 | 12 | public AuditLogsMiddleware(RequestDelegate next) 13 | { 14 | this.next = next; 15 | } 16 | 17 | public async Task InvokeAsync( 18 | HttpContext httpContext, 19 | TDbContext dbContext, 20 | IBus bus, 21 | AuditLogsPublisher auditLogsPublisher 22 | ) 23 | { 24 | await next(httpContext); 25 | await auditLogsPublisher.ExtractAndPublishAsync( 26 | dbContext, 27 | bus, 28 | httpContext.Request.Path.ToString(), 29 | httpContext.RequestAborted 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/EntityData.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace LeanCode.AuditLogs; 4 | 5 | public record EntityData( 6 | IReadOnlyList Ids, 7 | string Type, 8 | JsonDocument Changes, 9 | JsonDocument ShadowProperties, 10 | string EntityState 11 | ); 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/IAuditLogStorage.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.AuditLogs; 2 | 3 | public interface IAuditLogStorage 4 | { 5 | Task StoreEventAsync(AuditLogMessage auditLogMessage, CancellationToken cancellationToken); 6 | } 7 | 8 | public record AuditLogMessage( 9 | EntityData EntityChanged, 10 | string ActionName, 11 | DateTimeOffset DateOccurred, 12 | string? ActorId, 13 | string? TraceId, 14 | string? SpanId 15 | ); 16 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AuditLogs/MassTransintRegistrationConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | 3 | namespace LeanCode.AuditLogs; 4 | 5 | public static class MassTransitRegistrationConfigurationExtensions 6 | { 7 | public static void AddAuditLogsConsumer(this IRegistrationConfigurator configurator) 8 | { 9 | configurator.AddConsumer(typeof(AuditLogsConsumer), typeof(AuditLogsConsumerDefinition)); 10 | } 11 | 12 | public static void AddAuditLogsConsumer(this IRegistrationConfigurator configurator) 13 | { 14 | configurator.AddConsumer(typeof(AuditLogsConsumer), typeof(T)); 15 | } 16 | } 17 | 18 | public class AuditLogsConsumerDefinition : ConsumerDefinition 19 | { 20 | protected override void ConfigureConsumer( 21 | IReceiveEndpointConfigurator endpointConfigurator, 22 | IConsumerConfigurator consumerConfigurator, 23 | IRegistrationContext context 24 | ) 25 | { 26 | endpointConfigurator.UseMessageRetry(r => 27 | r.Immediate(1).Incremental(3, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AzureIdentity/AzureCredentialConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.AzureIdentity; 2 | 3 | public record AzureCredentialConfiguration 4 | { 5 | public string? TenantId { get; init; } 6 | public string? ClientId { get; init; } 7 | public string? ClientSecret { get; init; } 8 | public bool UseManagedIdentity { get; init; } 9 | public bool UseAzureCLI { get; init; } 10 | public bool UseAzureWorkloadIdentity { get; init; } 11 | 12 | public static string TenantIdKey { get; set; } = "Azure:TenantId"; 13 | public static string ClientIdKey { get; set; } = "Azure:ClientId"; 14 | public static string ClientSecretKey { get; set; } = "Azure:ClientSecret"; 15 | public static string UseManagedIdentityKey { get; set; } = "Azure:UseManagedIdentity"; 16 | public static string UseAzureCLIKey { get; set; } = "Azure:UseAzureCLI"; 17 | public static string UseAzureWorkloadIdentityKey { get; set; } = "Azure:UseAzureWorkloadIdentity"; 18 | } 19 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.AzureIdentity/LeanCode.AzureIdentity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ClientCredentialsHandler/ClientCredentialsConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ClientCredentialsHandler; 2 | 3 | public class ClientCredentialsConfiguration 4 | { 5 | public string ServerAddress { get; set; } = ""; 6 | public string ClientId { get; set; } = ""; 7 | public string ClientSecret { get; set; } = ""; 8 | public string Scopes { get; set; } = ""; 9 | } 10 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ClientCredentialsHandler/LeanCode.ClientCredentialsHandler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ConfigCat/ConfigCatInitializer.cs: -------------------------------------------------------------------------------- 1 | using ConfigCat.Client; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace LeanCode.ConfigCat; 5 | 6 | public sealed class ConfigCatInitializer : BackgroundService 7 | { 8 | private readonly IConfigCatClient configCatClient; 9 | 10 | public ConfigCatInitializer(IConfigCatClient configCatClient) 11 | { 12 | this.configCatClient = configCatClient; 13 | } 14 | 15 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 16 | { 17 | await configCatClient.GetAllKeysAsync(stoppingToken); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ConfigCat/ConfigCatOptions.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ConfigCat; 2 | 3 | public sealed record class ConfigCatOptions( 4 | string? SdkKey, 5 | string? FlagOverridesFilePath, 6 | string? FlagOverridesJsonObject 7 | ) 8 | { 9 | public ConfigCatOptions() 10 | : this(default, default, default) { } 11 | }; 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ConfigCat/LeanCode.ConfigCat.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Dapper/LeanCode.Dapper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Dapper/RawSqlQueryAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Dapper; 2 | 3 | [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] 4 | public sealed class RawSqlQueryAttribute : Attribute { } 5 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.EFMigrator/LeanCode.EFMigrator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/FCMSendException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Firebase.FCM; 2 | 3 | public class FCMSendException : Exception 4 | { 5 | public FCMSendException() 6 | : base("There was an error with one or more push notifications.") { } 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/IPushNotificationTokenStore.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Firebase.FCM; 2 | 3 | public interface IPushNotificationTokenStore 4 | where TUserId : IEquatable 5 | { 6 | const int MaxTokenBatchSize = 100; 7 | 8 | Task> GetTokensAsync(TUserId userId, CancellationToken cancellationToken = default); 9 | 10 | Task>> GetTokensAsync( 11 | IReadOnlySet userIds, 12 | CancellationToken cancellationToken = default 13 | ); 14 | 15 | Task AddUserTokenAsync(TUserId userId, string newToken, CancellationToken cancellationToken = default); 16 | Task RemoveUserTokenAsync(TUserId userId, string token, CancellationToken cancellationToken = default); 17 | 18 | Task RemoveTokenAsync(string token, CancellationToken cancellationToken = default); 19 | Task RemoveTokensAsync(IEnumerable tokens, CancellationToken cancellationToken = default); 20 | Task RemoveAllUserTokensAsync(TUserId userId, CancellationToken cancellationToken = default); 21 | } 22 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/LeanCode.Firebase.FCM.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/ModelBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace LeanCode.Firebase.FCM; 4 | 5 | public static class ModelBuilderExtensions 6 | { 7 | public static void ConfigurePushNotificationTokenEntity( 8 | this ModelBuilder builder, 9 | bool setTokenColumnMaxLength 10 | ) 11 | where TUserId : IEquatable 12 | { 13 | builder.Entity>(c => 14 | { 15 | c.HasKey(e => new { e.UserId, e.Token }); 16 | c.HasIndex(e => e.Token).IsUnique(); 17 | 18 | c.Property(e => e.UserId).ValueGeneratedNever(); 19 | 20 | var t = c.Property(e => e.Token).ValueGeneratedNever(); 21 | 22 | if (setTokenColumnMaxLength) 23 | { 24 | // https://stackoverflow.com/q/39959417 25 | t.HasMaxLength(512); 26 | } 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/Notifications.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Firebase.FCM; 2 | 3 | public static class Notifications 4 | { 5 | private static readonly NotificationDataConverter SharedConverter = NotificationDataConverter.New().Build(); 6 | 7 | /// 8 | /// Converts POCO object to a notification data dictionary. Does not support hierarchical 9 | /// data. 10 | /// 11 | [Obsolete("Use `NotificationDataConverter.ToNotificationData` instead` ")] 12 | public static Dictionary ToNotificationData(object data) 13 | { 14 | return SharedConverter.ToNotificationData(data); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.FCM/PushNotificationTokenEntity.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Firebase.FCM; 2 | 3 | public sealed record class PushNotificationTokenEntity(TUserId UserId, string Token, DateTime DateCreated) 4 | where TUserId : IEquatable; 5 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.Firestore/FirestoreModule.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace LeanCode.Firebase.Firestore; 6 | 7 | public static class FirestoreServiceProviderExtensions 8 | { 9 | public static void AddFirestore(this IServiceCollection services) 10 | { 11 | services.TryAddSingleton(); 12 | services.AddSingleton(sp => sp.GetRequiredService()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase.Firestore/LeanCode.Firebase.Firestore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Firebase/LeanCode.Firebase.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.Contracts.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.Contracts.pb -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate.Contracts/LeanCode.ForceUpdate.Contracts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | annotations 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | %(Filename)%(Extension) 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate.Contracts/VersionSupport.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.ForceUpdate.Contracts; 4 | 5 | public class VersionSupport : IQuery 6 | { 7 | public PlatformDTO Platform { get; set; } 8 | public string Version { get; set; } 9 | } 10 | 11 | public class VersionSupportDTO 12 | { 13 | public string CurrentlySupportedVersion { get; set; } 14 | public string MinimumRequiredVersion { get; set; } 15 | public VersionSupportResultDTO Result { get; set; } 16 | } 17 | 18 | public enum PlatformDTO 19 | { 20 | Android = 0, 21 | IOS = 1, 22 | } 23 | 24 | public enum VersionSupportResultDTO 25 | { 26 | UpdateRequired = 0, 27 | UpdateSuggested = 1, 28 | UpToDate = 2, 29 | } 30 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate/CQRSServicesBuilderCQRSExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Components; 2 | using LeanCode.CQRS.AspNetCore; 3 | using LeanCode.ForceUpdate.Contracts; 4 | using LeanCode.ForceUpdate.Services; 5 | using LeanCode.ForceUpdate.Services.CQRS; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace LeanCode.ForceUpdate; 9 | 10 | public static class CQRSServicesBuilderCQRSExtensions 11 | { 12 | public static CQRSServicesBuilder AddForceUpdate( 13 | this CQRSServicesBuilder cqrsServicesBuilder, 14 | AndroidVersionsConfiguration androidConfiguration, 15 | IOSVersionsConfiguration iOSConfiguration 16 | ) 17 | { 18 | cqrsServicesBuilder.Services.AddTransient(); 19 | 20 | cqrsServicesBuilder.Services.AddSingleton(androidConfiguration); 21 | 22 | cqrsServicesBuilder.Services.AddSingleton(iOSConfiguration); 23 | 24 | return cqrsServicesBuilder.AddCQRSObjects( 25 | TypesCatalog.Of(), 26 | TypesCatalog.Of() 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate/LeanCode.ForceUpdate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ForceUpdate/VersionsConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ForceUpdate; 2 | 3 | public sealed record IOSVersionsConfiguration(Version MinimumRequiredVersion, Version CurrentlySupportedVersion); 4 | 5 | public sealed record AndroidVersionsConfiguration(Version MinimumRequiredVersion, Version CurrentlySupportedVersion); 6 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Kratos/KratosAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.Kratos.Client.Model; 3 | using Microsoft.AspNetCore.Authentication; 4 | 5 | namespace LeanCode.Kratos; 6 | 7 | public class KratosAuthenticationOptions : AuthenticationSchemeOptions 8 | { 9 | public bool AllowInactiveIdentities { get; set; } 10 | public string SessionCookieName { get; set; } = KratosDefaults.SessionCookieName; 11 | public string NameClaimType { get; set; } = null!; 12 | public string RoleClaimType { get; set; } = null!; 13 | public Action> ClaimsExtractor { get; set; } = null!; 14 | } 15 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Kratos/KratosDefaults.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Kratos; 2 | 3 | public static class KratosDefaults 4 | { 5 | public const string AuthenticationScheme = "Kratos"; 6 | public const string SessionCookieName = "ory_kratos_session"; 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Kratos/LeanCode.Kratos.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/LeanCode.Localization.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/LocalizationConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Localization; 2 | 3 | public sealed class LocalizationConfiguration 4 | { 5 | public Type ResourceSource { get; } 6 | 7 | public LocalizationConfiguration(Type resourceSource) 8 | { 9 | ResourceSource = resourceSource; 10 | } 11 | 12 | public static LocalizationConfiguration For() => new LocalizationConfiguration(typeof(T)); 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/LocalizationServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Localization.StringLocalizers; 2 | using LeanCode.Logging; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace LeanCode.Localization; 6 | 7 | public static class LocalizationServiceProviderExtensions 8 | { 9 | public static IServiceCollection AddStringLocalizer( 10 | this IServiceCollection services, 11 | LocalizationConfiguration config 12 | ) 13 | { 14 | return services.AddSingleton(sp => new ResourceManagerStringLocalizer( 15 | config, 16 | sp.GetRequiredService>() 17 | )); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/LocalizedResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Localization; 2 | 3 | public class LocalizedResourceNotFoundException : Exception 4 | { 5 | public LocalizedResourceNotFoundException(Exception inner) 6 | : base(inner.Message, inner) { } 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/StringLocalizers/IStringLocalizer.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace LeanCode.Localization.StringLocalizers; 4 | 5 | /// 6 | /// Represents a service that provides localized strings for any given culture. 7 | /// 8 | public interface IStringLocalizer 9 | { 10 | /// 11 | /// Gets the string resource with the given name and localized for the specified culture. 12 | /// 13 | /// The to get the string for. 14 | /// The name of the string resource. 15 | /// The localized string. 16 | /// One or both arguments are `null`. 17 | /// Localized resource could not be found. 18 | string this[CultureInfo culture, string name] { get; } 19 | } 20 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Localization/StringLocalizers/IStringLocalizerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace LeanCode.Localization.StringLocalizers; 4 | 5 | public static class IStringLocalizerExtensions 6 | { 7 | public static string Format( 8 | this IStringLocalizer @this, 9 | CultureInfo culture, 10 | string name, 11 | params object[] arguments 12 | ) 13 | { 14 | return string.Format(culture, @this[culture, name], arguments); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging.AspNetCore/LeanCode.Logging.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging.AspNetCore/SerilogExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Serilog; 3 | using Serilog.Core; 4 | 5 | namespace LeanCode.Logging.AspNetCore; 6 | 7 | internal static class SerilogExtensions 8 | { 9 | internal static LoggerConfiguration DestructureCommonObjects( 10 | this LoggerConfiguration config, 11 | IEnumerable? searchAssemblies 12 | ) 13 | { 14 | if (searchAssemblies != null) 15 | { 16 | return SelectTypes(searchAssemblies) 17 | .Aggregate(config, (a, s) => a.Destructure.With(s)); 18 | } 19 | else 20 | { 21 | return config; 22 | } 23 | } 24 | 25 | private static List SelectTypes(IEnumerable searchAssemblies) 26 | { 27 | return searchAssemblies 28 | .SelectMany(a => a.ExportedTypes) 29 | .Where(t => typeof(TType).IsAssignableFrom(t) && t.IsPublic && t.GetConstructor(Type.EmptyTypes) != null) 30 | .Select(Activator.CreateInstance) 31 | .Cast() 32 | .ToList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging/ContextualLogger.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Events; 3 | 4 | namespace LeanCode.Logging; 5 | 6 | public class ContextualLogger(ILogger logger) : ILogger 7 | { 8 | private readonly ILogger logger = logger.ForContext(); 9 | 10 | public void Write(LogEvent logEvent) => logger.Write(logEvent); 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging/ILogger.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Logging; 2 | 3 | public interface ILogger : Serilog.ILogger 4 | { 5 | public new ILogger ForContext() => 6 | this is NullLogger ? NullLogger.Instance : new ContextualLogger(this); 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging/LeanCode.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging/LeanCodeLoggingBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace LeanCode.Logging; 5 | 6 | public static class LeanCodeLoggingBuilderExtensions 7 | { 8 | public static ILoggingBuilder AddContextualLeanCodeLogger(this ILoggingBuilder builder) 9 | { 10 | builder.Services.AddSingleton(typeof(ILogger<>), typeof(ContextualLogger<>)); 11 | 12 | return builder; 13 | } 14 | 15 | public static ILoggingBuilder AddNullLeanCodeLogger(this ILoggingBuilder builder) 16 | { 17 | builder.Services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>)); 18 | 19 | return builder; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Logging/NullLogger.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | 3 | namespace LeanCode.Logging; 4 | 5 | public class NullLogger : ILogger 6 | { 7 | public static readonly NullLogger Instance = new(); 8 | 9 | public void Write(LogEvent logEvent) { } 10 | } 11 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Mixpanel/IMixpanelEvent.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Mixpanel; 2 | 3 | public interface IMixpanelEvent 4 | { 5 | string EventName { get; } 6 | Dictionary Properties { get; } 7 | } 8 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Mixpanel/LeanCode.Mixpanel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Mixpanel/MixpanelConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.Mixpanel; 2 | 3 | public class MixpanelConfiguration 4 | { 5 | public string Token { get; set; } = ""; 6 | public string ApiKey { get; set; } = ""; 7 | public bool VerboseErrors { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Mixpanel/MixpanelServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace LeanCode.Mixpanel; 4 | 5 | public static class MixpanelServiceCollectionExtensions 6 | { 7 | public static IServiceCollection AddMixpanel(this IServiceCollection services, MixpanelConfiguration config) 8 | { 9 | services.AddSingleton(config); 10 | services.AddHttpClient(c => c.BaseAddress = new Uri("https://api.mixpanel.com")); 11 | return services; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Npgsql.ActiveDirectory/LeanCode.Npgsql.ActiveDirectory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Npgsql.ActiveDirectory/NpgsqlDataSourceBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | using Npgsql; 3 | 4 | namespace LeanCode.Npgsql.ActiveDirectory; 5 | 6 | public static class NpgsqlDataSourceBuilderExtensions 7 | { 8 | private static readonly string[] Scopes = new[] { "https://ossrdbms-aad.database.windows.net/.default" }; 9 | 10 | public static NpgsqlDataSourceBuilder UseAzureActiveDirectoryAuthentication( 11 | this NpgsqlDataSourceBuilder builder, 12 | TokenCredential credential 13 | ) 14 | { 15 | return builder.UsePeriodicPasswordProvider( 16 | async (_, ct) => 17 | { 18 | var token = await credential.GetTokenAsync(new(Scopes, null), ct); 19 | 20 | return token.Token; 21 | }, 22 | TimeSpan.FromMinutes(5), 23 | TimeSpan.FromSeconds(1) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.OpenTelemetry/LeanCode.OpenTelemetry.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.OpenTelemetry/LeanCodeActivitySource.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace LeanCode.OpenTelemetry; 4 | 5 | public static class LeanCodeActivitySource 6 | { 7 | public static readonly ActivitySource ActivitySource = new("LeanCode.CoreLibrary"); 8 | 9 | public static Activity? Start(string name) => ActivitySource.StartActivity(name); 10 | 11 | public static Activity? StartExecution(string type, string? objectType) => Start($"{type} - {objectType}"); 12 | 13 | public static Activity? StartMiddleware(string middlewareName) => Start($"middleware - {middlewareName}"); 14 | 15 | public static Activity? StartMiddleware(string middlewareName, string? objectName) => 16 | Start($"middleware - {middlewareName} - {objectName}"); 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.OpenTelemetry/LeanCodeMetrics.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.OpenTelemetry; 2 | 3 | public static class LeanCodeMetrics 4 | { 5 | public const string MeterName = "LeanCode.CoreLibrary"; 6 | } 7 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.OpenTelemetry/MeterBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using OpenTelemetry.Metrics; 2 | 3 | namespace LeanCode.OpenTelemetry; 4 | 5 | public static class MeterBuilderExtensions 6 | { 7 | public static MeterProviderBuilder AddLeanCodeInstrumentation(this MeterProviderBuilder builder) 8 | { 9 | return builder.AddMeter(LeanCodeMetrics.MeterName); 10 | } 11 | 12 | [Obsolete($"Use {nameof(AddLeanCodeInstrumentation)} instead.")] 13 | public static MeterProviderBuilder AddLeanCodeMetrics(this MeterProviderBuilder builder) 14 | { 15 | return builder.AddLeanCodeInstrumentation(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.OpenTelemetry/TracerBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using OpenTelemetry.Trace; 2 | 3 | namespace LeanCode.OpenTelemetry; 4 | 5 | public static class TracerProviderBuilderExtensions 6 | { 7 | public static TracerProviderBuilder AddLeanCodeInstrumentation(this TracerProviderBuilder builder) 8 | { 9 | return builder.AddSource(LeanCodeActivitySource.ActivitySource.Name); 10 | } 11 | 12 | [Obsolete($"Use {nameof(AddLeanCodeInstrumentation)} instead.")] 13 | public static TracerProviderBuilder AddLeanCodeTelemetry(this TracerProviderBuilder builder) 14 | { 15 | return builder.AddLeanCodeInstrumentation(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.PeriodicService/IPeriodicAction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Cronos; 3 | 4 | namespace LeanCode.PeriodicService; 5 | 6 | public interface IPeriodicAction 7 | { 8 | [SuppressMessage("?", "CA1716", Justification = "Convention for `PeriodicAction`.")] 9 | CronExpression When { get; } 10 | bool SkipFirstExecution { get; } 11 | 12 | [SuppressMessage("?", "LNCD0006", Justification = "Convention for `PeriodicAction`.")] 13 | Task ExecuteAsync(CancellationToken stoppingToken); 14 | } 15 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.PeriodicService/LeanCode.PeriodicService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.PeriodicService/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace LeanCode.PeriodicService; 4 | 5 | public static class ServiceCollectionExtensions 6 | { 7 | public static IServiceCollection RegisterPeriodicAction(this IServiceCollection builder) 8 | where T : class, IPeriodicAction 9 | { 10 | builder.AddTransient(); 11 | return builder.AddHostedService>(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SendGrid/LeanCode.SendGrid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SendGrid/SendGridException.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Net; 3 | 4 | namespace LeanCode.SendGrid; 5 | 6 | public class SendGridException : Exception 7 | { 8 | public HttpStatusCode ResponseStatusCode { get; } 9 | public ImmutableArray ErrorMessages { get; } = ImmutableArray.Empty; 10 | 11 | internal SendGridException(HttpStatusCode responseStatusCode, IEnumerable? errors) 12 | : base($"SendGrid request failed with status code {responseStatusCode}.") 13 | { 14 | ResponseStatusCode = responseStatusCode; 15 | 16 | if (errors is object) 17 | { 18 | ErrorMessages = errors.Select(e => e.Message!).Where(m => m is object).ToImmutableArray(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SendGrid/SendGridRazorMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using SendGrid.Helpers.Mail; 3 | 4 | namespace LeanCode.SendGrid; 5 | 6 | public class SendGridRazorMessage : SendGridMessage 7 | { 8 | [JsonIgnore] 9 | public object? PlainTextContentModel { get; set; } 10 | 11 | [JsonIgnore] 12 | public object? HtmlContentModel { get; set; } 13 | 14 | internal virtual IEnumerable GetTemplateNames(string templateBaseName) 15 | { 16 | yield return templateBaseName; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SendGrid/SendGridResponse.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.SendGrid; 2 | 3 | internal sealed class SendGridResponse 4 | { 5 | public SendGridError[]? Errors { get; set; } 6 | } 7 | 8 | internal sealed class SendGridError 9 | { 10 | public string? Message { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SendGrid/SendGridServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection.Extensions; 3 | using SendGrid; 4 | 5 | namespace LeanCode.SendGrid; 6 | 7 | public static class SendGridServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddSendGridClient(this IServiceCollection services, SendGridClientOptions config) 10 | { 11 | services.AddSingleton(config); 12 | services.TryAddTransient(s => new SendGridClient(s.GetRequiredService())); 13 | services.TryAddTransient(); 14 | return services; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.Serialization/LeanCode.Serialization.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SmsSender/Exceptions/Exceptions.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.SmsSender.Exceptions; 2 | 3 | public abstract class ResponseException : Exception 4 | { 5 | public int ErrorCode { get; private set; } 6 | 7 | protected ResponseException(int errorCode, string message) 8 | : base(message) 9 | { 10 | ErrorCode = errorCode; 11 | } 12 | } 13 | 14 | public class ActionException : ResponseException 15 | { 16 | public ActionException(int errorCode, string message) 17 | : base(errorCode, message) { } 18 | } 19 | 20 | public class ClientException : ResponseException 21 | { 22 | public ClientException(int errorCode, string message) 23 | : base(errorCode, message) { } 24 | } 25 | 26 | public class HostException : ResponseException 27 | { 28 | public HostException(int errorCode, string message) 29 | : base(errorCode, message) { } 30 | } 31 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SmsSender/ISmsSender.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.SmsSender; 2 | 3 | public interface ISmsSender 4 | { 5 | Task SendAsync(string message, string phoneNumber, CancellationToken cancellationToken = default); 6 | } 7 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SmsSender/LeanCode.SmsSender.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.SmsSender/SmsSenderServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace LeanCode.SmsSender; 4 | 5 | public static class SmsSenderServiceCollectionExtensions 6 | { 7 | public static IServiceCollection AddSmsSender(this IServiceCollection services, SmsApiConfiguration config) 8 | { 9 | services.AddSingleton(config); 10 | services.AddHttpClient( 11 | (sp, c) => 12 | { 13 | var config = sp.GetRequiredService(); 14 | 15 | SmsApiClient.ConfigureHttpClient(config, c); 16 | } 17 | ); 18 | 19 | return services; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/Extensions/Layout.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.Language; 2 | 3 | namespace LeanCode.ViewRenderer.Razor.Extensions; 4 | 5 | internal class Layout 6 | { 7 | public const string DirectiveName = "layout"; 8 | 9 | public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateSingleLineDirective( 10 | DirectiveName, 11 | b => b.AddMemberToken() 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/Extensions/LayoutDirectivePass.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Razor.Language; 2 | using Microsoft.AspNetCore.Razor.Language.Intermediate; 3 | 4 | namespace LeanCode.ViewRenderer.Razor.Extensions; 5 | 6 | internal class LayoutDirectivePass : RazorEngineFeatureBase, IRazorDirectiveClassifierPass 7 | { 8 | public int Order => 1001; 9 | 10 | public void Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) 11 | { 12 | var @class = documentNode.FindPrimaryClass(); 13 | 14 | var layout = documentNode 15 | .FindDirectiveReferences(Layout.Directive) 16 | .SelectMany(d => ((DirectiveIntermediateNode)d.Node).Tokens) 17 | .FirstOrDefault(t => t != null); 18 | 19 | var prop = new LayoutNode(layout?.Content); 20 | 21 | @class.Children.Add(prop); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/Internals.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | // NSubsitute 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 5 | [assembly: InternalsVisibleTo("LeanCode.ViewRenderer.Razor.Tests")] 6 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/LeanCode.ViewRenderer.Razor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/RazorViewRendererOptions.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ViewRenderer.Razor; 2 | 3 | public class RazorViewRendererOptions 4 | { 5 | public const string DefaultExtension = ".cshtml"; 6 | 7 | public IReadOnlyList ViewLocations { get; } 8 | public string Extension { get; } 9 | 10 | public RazorViewRendererOptions(IReadOnlyList viewLocations, string extension = DefaultExtension) 11 | { 12 | ViewLocations = viewLocations; 13 | Extension = extension; 14 | } 15 | 16 | public RazorViewRendererOptions(params string[] viewLocations) 17 | { 18 | ViewLocations = viewLocations; 19 | Extension = DefaultExtension; 20 | } 21 | 22 | public RazorViewRendererOptions(string extension, string[] viewLocations) 23 | { 24 | ViewLocations = viewLocations; 25 | Extension = extension; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/RazorViewRendererServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Logging; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace LeanCode.ViewRenderer.Razor; 5 | 6 | public static class RazorViewRendererServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddRazorViewRenderer( 9 | this IServiceCollection services, 10 | RazorViewRendererOptions config 11 | ) 12 | { 13 | return services.AddSingleton(sp => new RazorViewRenderer( 14 | config, 15 | sp.GetRequiredService>() 16 | )); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/ViewBase/AttributeValue.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ViewRenderer.Razor.ViewBase; 2 | 3 | public class AttributeValue 4 | { 5 | public string Prefix { get; } 6 | public object Value { get; } 7 | public bool Literal { get; } 8 | 9 | public AttributeValue(string prefix, object value, bool literal) 10 | { 11 | Prefix = prefix; 12 | Value = value; 13 | Literal = literal; 14 | } 15 | 16 | public static AttributeValue FromTuple(Tuple value) => 17 | new AttributeValue(value.Item1, value.Item2, value.Item3); 18 | 19 | public static AttributeValue FromTuple(Tuple value) => 20 | new AttributeValue(value.Item1, value.Item2, value.Item3); 21 | 22 | public static implicit operator AttributeValue(Tuple value) => FromTuple(value); 23 | } 24 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer.Razor/ViewBase/HelperResult.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ViewRenderer.Razor.ViewBase; 2 | 3 | public class HelperResult 4 | { 5 | public Action WriteAction { get; } 6 | 7 | public HelperResult(Action action) 8 | { 9 | WriteAction = action; 10 | } 11 | 12 | public void WriteTo(TextWriter writer) 13 | { 14 | WriteAction(writer); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer/IViewRenderer.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ViewRenderer; 2 | 3 | public interface IViewRenderer 4 | { 5 | /// 6 | /// is generally faster alternative, use that if possible. 7 | /// 8 | /// should be a public type because currently 9 | /// the (only) implementation based on Razor uses dynamic types and the object binder 10 | /// respects the visibility. 11 | /// 12 | Task RenderToStringAsync(string viewName, object model, CancellationToken cancellationToken = default); 13 | 14 | /// 15 | /// should be a public type because currently 16 | /// the (only) implementation based on Razor uses dynamic types and the object binder 17 | /// respects the visibility. 18 | /// 19 | Task RenderToStreamAsync( 20 | string viewName, 21 | object model, 22 | Stream outputStream, 23 | CancellationToken cancellationToken = default 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer/LeanCode.ViewRenderer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Infrastructure/LeanCode.ViewRenderer/ViewNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.ViewRenderer; 2 | 3 | public class ViewNotFoundException : Exception 4 | { 5 | public string? ViewName { get; } 6 | 7 | public ViewNotFoundException(string? viewName, string message) 8 | : base(message) 9 | { 10 | ViewName = viewName; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Testing/LeanCode.DomainModels.EventsExecution.TestHelpers/LeanCode.DomainModels.EventsExecution.TestHelpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Testing/LeanCode.IntegrationTestHelpers/HttpClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using System.Security.Claims; 3 | 4 | namespace LeanCode.IntegrationTestHelpers; 5 | 6 | public static class HttpClientExtensions 7 | { 8 | public static HttpClient UseTestAuthorization(this HttpClient client, ClaimsPrincipal principal) 9 | { 10 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( 11 | TestAuthenticationHandler.SchemeName, 12 | TestAuthenticationHandler.SerializePrincipal(principal) 13 | ); 14 | return client; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Testing/LeanCode.TimeProvider.TestHelpers/LeanCode.TimeProvider.TestHelpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Testing/LeanCode.TimeProvider.TestHelpers/TestTimeProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Time.Testing; 2 | 3 | namespace LeanCode.TimeProvider.TestHelpers; 4 | 5 | public static class TestTimeProvider 6 | { 7 | public static FakeTimeProvider ActivateFake(DateTime epoch, TimeZoneInfo? localTimeZone = null) => 8 | ActivateFake(new DateTimeOffset(epoch, TimeSpan.Zero), localTimeZone); 9 | 10 | public static FakeTimeProvider ActivateFake(DateTimeOffset epoch, TimeZoneInfo? localTimeZone = null) 11 | { 12 | var provider = new FakeTimeProvider(epoch.ToUniversalTime()); 13 | 14 | if (localTimeZone is not null) 15 | { 16 | provider.SetLocalTimeZone(localTimeZone); 17 | } 18 | 19 | AsyncLocalTimeProvider.Activate(provider); 20 | 21 | return provider; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/AnalyzerReleases.Shipped.md: -------------------------------------------------------------------------------- 1 | ## Release 1.0 2 | 3 | ### New Rules 4 | Rule ID -| Category | Severity | Notes 5 | ---------|----------|----------|-------------------- 6 | LNCD0001 | Security | Error | EnsureCommandsAndQueriesHaveAuthorizers 7 | LNCD0002 | Security | Error | EnsureCommandsAndQueriesHaveAuthorizers 8 | LNCD0003 | Usage | Info | SuggestCommandsHaveValidators 9 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/CodeFixProviders/AddCommandValidatorCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using LeanCode.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace LeanCode.CodeAnalysis.CodeFixProviders; 8 | 9 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddAuthorizationAttributeCodeFixProvider))] 10 | [Shared] 11 | public class AddCommandValidatorCodeFixProvider : CodeFixProvider 12 | { 13 | public override ImmutableArray FixableDiagnosticIds { get; } = 14 | ImmutableArray.Create(DiagnosticsIds.CommandsShouldHaveValidators); 15 | 16 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 17 | 18 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 19 | { 20 | context.RegisterCodeFix(new AddCommandValidatorCodeAction(context.Document, context.Span), context.Diagnostics); 21 | 22 | return Task.CompletedTask; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/CodeFixProviders/FixCQRSHandlerNamespaceCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using LeanCode.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace LeanCode.CodeAnalysis.CodeFixProviders; 8 | 9 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FixCQRSHandlerNamespaceCodeFixProvider))] 10 | [Shared] 11 | public class FixCQRSHandlerNamespaceCodeFixProvider : CodeFixProvider 12 | { 13 | public override ImmutableArray FixableDiagnosticIds { get; } = 14 | ImmutableArray.Create(DiagnosticsIds.CQRSHandlersShouldBeInProperNamespace); 15 | 16 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 17 | 18 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 19 | { 20 | context.RegisterCodeFix( 21 | new FixCQRSHandlerNamespaceCodeAction(context.Document, context.Span), 22 | context.Diagnostics 23 | ); 24 | 25 | return Task.CompletedTask; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/CodeFixProviders/FixCancellationTokenNamingCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using LeanCode.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace LeanCode.CodeAnalysis.CodeFixProviders; 8 | 9 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FixCancellationTokenNamingCodeFixProvider))] 10 | [Shared] 11 | public class FixCancellationTokenNamingCodeFixProvider : CodeFixProvider 12 | { 13 | public override ImmutableArray FixableDiagnosticIds { get; } = 14 | ImmutableArray.Create(DiagnosticsIds.CancellationTokensShouldFollowNamingConvention); 15 | 16 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 17 | 18 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 19 | { 20 | context.RegisterCodeFix( 21 | new FixCancellationTokenNamingCodeAction(context.Document, context.Span), 22 | context.Diagnostics 23 | ); 24 | 25 | return Task.CompletedTask; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/CodeFixProviders/FixCommandValidatorNamingCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using LeanCode.CodeAnalysis.CodeActions; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace LeanCode.CodeAnalysis.CodeFixProviders; 8 | 9 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FixCommandValidatorNamingCodeFixProvider))] 10 | [Shared] 11 | public class FixCommandValidatorNamingCodeFixProvider : CodeFixProvider 12 | { 13 | public override ImmutableArray FixableDiagnosticIds { get; } = 14 | ImmutableArray.Create(DiagnosticsIds.CommandValidatorsShouldFollowNamingConvention); 15 | 16 | public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 17 | 18 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 19 | { 20 | context.RegisterCodeFix( 21 | new FixCommandValidatorNamingCodeAction(context.Document, context.Span), 22 | context.Diagnostics 23 | ); 24 | 25 | return Task.CompletedTask; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/DiagnosticsIds.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CodeAnalysis; 2 | 3 | public static class DiagnosticsIds 4 | { 5 | public const string CommandsShouldHaveAuthorizers = "LNCD0001"; 6 | public const string QueriesShouldHaveAuthorizers = "LNCD0002"; 7 | public const string CommandsShouldHaveValidators = "LNCD0003"; 8 | public const string OperationsShouldHaveAuthorizers = "LNCD0004"; 9 | public const string TypedIdMustBeStruct = "LNCD0005"; 10 | public const string CancellationTokensShouldFollowNamingConvention = "LNCD0006"; 11 | public const string CommandHandlersShouldFollowNamingConvention = "LNCD0007"; 12 | public const string QueryHandlersShouldFollowNamingConvention = "LNCD0008"; 13 | public const string OperationHandlersShouldFollowNamingConvention = "LNCD0009"; 14 | public const string CommandValidatorsShouldFollowNamingConvention = "LNCD0010"; 15 | public const string CQRSHandlersShouldBeInProperNamespace = "LNCD0011"; 16 | } 17 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/LeanCode.CodeAnalysis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | true 7 | 8 | 9 | netstandard2.1 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Tools/LeanCode.CodeAnalysis/local-debug.txt: -------------------------------------------------------------------------------- 1 | For playing with code analyzers without referencing it as a nuget package, 2 | reference the project in the following way. 3 | 4 | 5 | false 6 | Analyzer 7 | 8 | -------------------------------------------------------------------------------- /test-bed/Api/TestQuery.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | 4 | namespace LeanCode.TestBed.Api; 5 | 6 | [Obsolete("For tests.")] 7 | public class TestQuery : IQuery { } 8 | 9 | public class TestQueryResult 10 | { 11 | public Guid Id { get; set; } 12 | public string Property1 { get; set; } = default!; 13 | public TestQueryResult? Inner { get; set; } 14 | } 15 | 16 | [Obsolete("For tests.")] 17 | public class TestQueryQH : IQueryHandler 18 | { 19 | public Task ExecuteAsync(HttpContext context, TestQuery query) 20 | { 21 | return Task.FromResult( 22 | new TestQueryResult 23 | { 24 | Id = Guid.NewGuid(), 25 | Property1 = "Some value", 26 | Inner = new() { Id = Guid.NewGuid(), Property1 = "Other value" }, 27 | } 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test-bed/Program.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Components; 2 | using LeanCode.CQRS.AspNetCore; 3 | using LeanCode.CQRS.Validation.Fluent; 4 | using LeanCode.TestBed.Api; 5 | using Serilog; 6 | 7 | var builder = WebApplication.CreateBuilder(args); 8 | 9 | builder.Logging.AddSerilog(); 10 | builder.Services.AddFluentValidation(TypesCatalog.Of()); 11 | builder.Services.AddCQRS(TypesCatalog.Of(), TypesCatalog.Of()); 12 | builder.Services.AddCQRSApiExplorer(); 13 | builder.Services.AddOpenApi(); 14 | 15 | var app = builder.Build(); 16 | 17 | app.MapOpenApi(); 18 | app.MapRemoteCQRS( 19 | "/", 20 | c => 21 | { 22 | c.Commands = p => p.Secure().Validate(); 23 | c.Queries = p => p.Secure(); 24 | } 25 | ); 26 | app.UseReDoc(opt => 27 | { 28 | opt.SpecUrl("/openapi/v1.json"); 29 | opt.RoutePrefix = "redoc"; 30 | }); 31 | app.UseSwaggerUI(opt => 32 | { 33 | opt.SwaggerEndpoint("/openapi/v1.json", "Test API"); 34 | opt.RoutePrefix = "swagger"; 35 | }); 36 | 37 | app.Run(); 38 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests.Integration/CustomAuthorizer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Security.Claims; 3 | using LeanCode.Contracts.Security; 4 | using LeanCode.CQRS.Security; 5 | 6 | namespace LeanCode.CQRS.AspNetCore.Tests.Integration; 7 | 8 | public interface ICustomAuthorizerParams 9 | { 10 | bool FailAuthorization { get; set; } 11 | } 12 | 13 | [SuppressMessage("?", "CA1040", Justification = "Marker interface")] 14 | public interface ICustomAuthorizer { } 15 | 16 | public class CustomAuthorizer : CustomAuthorizer, ICustomAuthorizer 17 | { 18 | protected override Task CheckIfAuthorizedAsync( 19 | ClaimsPrincipal user, 20 | ICustomAuthorizerParams obj, 21 | CancellationToken cancellationToken 22 | ) 23 | { 24 | return Task.FromResult(!obj.FailAuthorization); 25 | } 26 | } 27 | 28 | public sealed class CustomAuthorizeWhenAttribute : AuthorizeWhenAttribute 29 | { 30 | public CustomAuthorizeWhenAttribute() 31 | : base(typeof(ICustomAuthorizer)) { } 32 | } 33 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests.Integration/HttpContextCustomAuthorizer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using LeanCode.Contracts.Security; 3 | using LeanCode.CQRS.Security; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace LeanCode.CQRS.AspNetCore.Tests.Integration; 7 | 8 | public interface IHttpContextCustomAuthorizerParams 9 | { 10 | bool FailAuthorization { get; set; } 11 | } 12 | 13 | [SuppressMessage("?", "CA1040", Justification = "Marker interface")] 14 | public interface IHttpContextCustomAuthorizer { } 15 | 16 | public class HttpContextCustomAuthorizer 17 | : HttpContextCustomAuthorizer, 18 | IHttpContextCustomAuthorizer 19 | { 20 | protected override Task CheckIfAuthorizedAsync(HttpContext context, IHttpContextCustomAuthorizerParams obj) 21 | { 22 | return Task.FromResult(!obj.FailAuthorization); 23 | } 24 | } 25 | 26 | public sealed class HttpContextCustomAuthorizeWhenAttribute : AuthorizeWhenAttribute 27 | { 28 | public HttpContextCustomAuthorizeWhenAttribute() 29 | : base(typeof(IHttpContextCustomAuthorizer)) { } 30 | } 31 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests.Integration/LeanCode.CQRS.AspNetCore.Tests.Integration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests.Integration/TestOperation.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Tests.Integration; 6 | 7 | [HttpContextCustomAuthorizeWhen] 8 | public class TestOperation : IOperation, IHttpContextCustomAuthorizerParams 9 | { 10 | public bool FailAuthorization { get; set; } 11 | 12 | public int X { get; set; } 13 | public int Y { get; set; } 14 | } 15 | 16 | public class TestOperationResult 17 | { 18 | public int Sum { get; set; } 19 | } 20 | 21 | public class TestOperationHandler : IOperationHandler 22 | { 23 | public Task ExecuteAsync(HttpContext context, TestOperation operation) 24 | { 25 | return Task.FromResult(new TestOperationResult { Sum = operation.X + operation.Y }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests.Integration/TestQuery.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Tests.Integration; 6 | 7 | [CustomAuthorizeWhen] 8 | public class TestQuery : IQuery, ICustomAuthorizerParams 9 | { 10 | public bool FailAuthorization { get; set; } 11 | 12 | public int X { get; set; } 13 | public int Y { get; set; } 14 | } 15 | 16 | public class TestQueryResult 17 | { 18 | public int Sum { get; set; } 19 | } 20 | 21 | public class TestQueryHandler : IQueryHandler 22 | { 23 | public Task ExecuteAsync(HttpContext context, TestQuery query) 24 | { 25 | return Task.FromResult(new TestQueryResult { Sum = query.X + query.Y }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/LeanCode.CQRS.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/Local/Context/LocalCallServiceProvidersFeatureTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LeanCode.CQRS.AspNetCore.Local.Context; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Xunit; 5 | 6 | namespace LeanCode.CQRS.AspNetCore.Tests.Local.Context; 7 | 8 | public class LocalCallServiceProvidersFeatureTests 9 | { 10 | private readonly IServiceProvider serviceProvider = new ServiceCollection().BuildServiceProvider(); 11 | 12 | [Fact] 13 | public void RequestServices_can_be_changed() 14 | { 15 | var feature = new LocalCallServiceProvidersFeature(serviceProvider); 16 | feature.RequestServices.Should().BeSameAs(serviceProvider); 17 | 18 | feature.RequestServices = new ServiceCollection().BuildServiceProvider(); 19 | feature.RequestServices.Should().NotBeSameAs(serviceProvider); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/Local/Context/NullEndpointFeatureTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LeanCode.CQRS.AspNetCore.Local.Context; 3 | using Xunit; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Tests.Local.Context; 6 | 7 | public class NullEndpointFeatureTests 8 | { 9 | [Fact] 10 | public void Endpoint_always_returns_null() 11 | { 12 | NullEndpointFeature.Empty.Endpoint.Should().BeNull(); 13 | NullEndpointFeature.Empty.Endpoint = new(null, null, null); 14 | NullEndpointFeature.Empty.Endpoint.Should().BeNull(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/Local/Context/NullRequestCookieCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LeanCode.CQRS.AspNetCore.Local.Context; 3 | using Xunit; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Tests.Local.Context; 6 | 7 | public class NullRequestCookieCollectionTests 8 | { 9 | [Fact] 10 | public void Count_should_be_zero() 11 | { 12 | NullRequestCookieCollection.Empty.Count.Should().Be(0); 13 | } 14 | 15 | [Fact] 16 | public void ContainsKey_should_always_return_false() 17 | { 18 | NullRequestCookieCollection.Empty.ContainsKey("").Should().BeFalse(); 19 | } 20 | 21 | [Fact] 22 | public void GetEnumerator_should_return_empty_enumerable() 23 | { 24 | using var enumerator = NullRequestCookieCollection.Empty.GetEnumerator(); 25 | enumerator.MoveNext().Should().BeFalse(); 26 | } 27 | 28 | [Fact] 29 | public void TryGetValue_should_always_return_false_and_null() 30 | { 31 | NullRequestCookieCollection.Empty.TryGetValue("", out var value).Should().BeFalse(); 32 | value.Should().BeNull(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/Local/Context/NullResponseCookiesTests.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.AspNetCore.Local.Context; 2 | using Xunit; 3 | 4 | namespace LeanCode.CQRS.AspNetCore.Tests.Local.Context; 5 | 6 | public class NullResponseCookiesTests 7 | { 8 | [Fact] 9 | [System.Diagnostics.CodeAnalysis.SuppressMessage( 10 | "Security", 11 | "CA5382:Use Secure Cookies In ASP.NET Core", 12 | Justification = "Tests." 13 | )] 14 | public void Append_should_not_throw() 15 | { 16 | NullResponseCookies.Empty.Append("", ""); 17 | } 18 | 19 | [Fact] 20 | public void Append_with_options_should_not_throw() 21 | { 22 | NullResponseCookies.Empty.Append("", "", new()); 23 | } 24 | 25 | [Fact] 26 | public void Delete_should_not_throw() 27 | { 28 | NullResponseCookies.Empty.Delete(""); 29 | } 30 | 31 | [Fact] 32 | public void Delete_with_options_should_not_throw() 33 | { 34 | NullResponseCookies.Empty.Delete("", new()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.AspNetCore.Tests/Local/Context/NullWebSocketManagerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using LeanCode.CQRS.AspNetCore.Local.Context; 3 | using Xunit; 4 | 5 | namespace LeanCode.CQRS.AspNetCore.Tests.Local.Context; 6 | 7 | public class NullWebSocketManagerTests 8 | { 9 | [Fact] 10 | public void IsWebSocketRequest_should_always_be_false() 11 | { 12 | NullWebSocketManager.Empty.IsWebSocketRequest.Should().BeFalse(); 13 | } 14 | 15 | [Fact] 16 | public void Request_protocols_should_be_empty_and_readonly() 17 | { 18 | NullWebSocketManager.Empty.WebSocketRequestedProtocols.Should().BeEmpty(); 19 | NullWebSocketManager.Empty.WebSocketRequestedProtocols.IsReadOnly.Should().BeTrue(); 20 | 21 | var act = () => NullWebSocketManager.Empty.WebSocketRequestedProtocols.Add(""); 22 | act.Should().Throw(); 23 | } 24 | 25 | [Fact] 26 | public async Task Accept_should_always_throw() 27 | { 28 | var act = () => NullWebSocketManager.Empty.AcceptWebSocketAsync((string?)null); 29 | await act.Should().ThrowAsync(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.MassTransitRelay.Tests/Integration/HandledLog.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CQRS.MassTransitRelay.Tests.Integration; 2 | 3 | /// 4 | /// The purpose here is rather to verify if entities are saved in the database along with published messages. 5 | /// 6 | public class HandledLog 7 | { 8 | public Guid CorrelationId { get; private set; } 9 | public string HandlerName { get; private set; } 10 | 11 | private HandledLog(Guid correlationId, string handlerName) 12 | { 13 | CorrelationId = correlationId; 14 | HandlerName = handlerName; 15 | } 16 | 17 | public static void Report(TestDbContext dbContext, Guid correlationId, string handlerName) 18 | { 19 | dbContext.HandledLog.Add(new(correlationId, handlerName)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.MassTransitRelay.Tests/Integration/TestCommand.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.Contracts.Security; 3 | using LeanCode.CQRS.Execution; 4 | using LeanCode.DomainModels.Model; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace LeanCode.CQRS.MassTransitRelay.Tests.Integration; 8 | 9 | [AllowUnauthorized] 10 | public class TestCommand : ICommand 11 | { 12 | public Guid CorrelationId { get; set; } 13 | } 14 | 15 | public class TestCommandHandler : ICommandHandler 16 | { 17 | private readonly TestDbContext dbContext; 18 | 19 | public TestCommandHandler(TestDbContext dbContext) 20 | { 21 | this.dbContext = dbContext; 22 | } 23 | 24 | public Task ExecuteAsync(HttpContext context, TestCommand command) 25 | { 26 | DomainEvents.Raise(new Event1(command.CorrelationId)); 27 | HandledLog.Report(dbContext, command.CorrelationId, nameof(TestCommandHandler)); 28 | return Task.CompletedTask; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.MassTransitRelay.Tests/Integration/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace LeanCode.CQRS.MassTransitRelay.Tests.Integration; 5 | 6 | public class TestDbContext : DbContext 7 | { 8 | public DbSet HandledLog => Set(); 9 | 10 | public TestDbContext(DbContextOptions opts) 11 | : base(opts) { } 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder.Entity(cfg => cfg.HasKey(e => new { e.CorrelationId, e.HandlerName })); 16 | 17 | modelBuilder.AddInboxStateEntity(); 18 | modelBuilder.AddOutboxMessageEntity(); 19 | modelBuilder.AddOutboxStateEntity(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.MassTransitRelay.Tests/LeanCode.CQRS.MassTransitRelay.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.RemoteHttp.Client.Tests/LeanCode.CQRS.RemoteHttp.Client.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.Security.Tests/LeanCode.CQRS.Security.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/CQRS/LeanCode.CQRS.Validation.Fluent.Tests/LeanCode.CQRS.Validation.Fluent.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Core/LeanCode.Components.Startup.Tests/LeanCode.Components.Startup.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LeanCode.Startup.Autofac.Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.DomainModels.EF.Tests/LeanCode.DomainModels.EF.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.DomainModels.EF.Tests/ModelConfigurationBuilderWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace LeanCode.DomainModels.EF.Tests; 7 | 8 | [SuppressMessage("?", "EF1001", Justification = "Tests.")] 9 | internal sealed class ModelConfigurationBuilderWrapper : ModelConfigurationBuilder 10 | { 11 | public ModelConfigurationBuilderWrapper() 12 | : base(new(), new ServiceCollection().BuildServiceProvider()) { } 13 | 14 | public ModelConfiguration Build() => ModelConfiguration; 15 | } 16 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.DomainModels.EF.Tests/TypedIds.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Ids; 2 | 3 | namespace LeanCode.DomainModels.EF.Tests; 4 | 5 | [TypedId(TypedIdFormat.RawInt)] 6 | public readonly partial record struct IntId; 7 | 8 | [TypedId(TypedIdFormat.RawLong)] 9 | public readonly partial record struct LongId; 10 | 11 | [TypedId(TypedIdFormat.RawGuid)] 12 | public readonly partial record struct GuidId; 13 | 14 | [TypedId(TypedIdFormat.PrefixedGuid)] 15 | public readonly partial record struct PrefixedGuidId; 16 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.DomainModels.EventsExecution.TestHelpers.Tests/Events.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | using LeanCode.TimeProvider; 3 | 4 | namespace LeanCode.DomainModels.EventsExecution.TestHelpers.Tests; 5 | 6 | internal sealed class SampleEvent1 : IDomainEvent 7 | { 8 | public Guid Id { get; } 9 | public DateTime DateOccurred { get; } 10 | 11 | public SampleEvent1(Guid id) 12 | { 13 | Id = id; 14 | DateOccurred = Time.UtcNow; 15 | } 16 | } 17 | 18 | internal sealed class SampleEvent2 : IDomainEvent 19 | { 20 | public Guid Id { get; } 21 | public DateTime DateOccurred { get; } 22 | 23 | public SampleEvent2(Guid id) 24 | { 25 | Id = id; 26 | DateOccurred = Time.UtcNow; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.DomainModels.EventsExecution.TestHelpers.Tests/LeanCode.DomainModels.EventsExecution.TestHelpers.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Domain/LeanCode.TimeProvider.Tests/LeanCode.TimeProvider.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/Helpers/LeanCode.UserIdExtractors.Tests/LeanCode.UserIdExtractors.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.AuditLogs.Tests/AuditLogsMiddlewareTests.cs: -------------------------------------------------------------------------------- 1 | using MassTransit; 2 | using Microsoft.AspNetCore.Http; 3 | using NSubstitute; 4 | using Xunit; 5 | 6 | namespace LeanCode.AuditLogs.Tests; 7 | 8 | public sealed class AuditLogsMiddlewareTests 9 | { 10 | [Fact] 11 | public async Task Extracts_changes_after_pipeline_execution() 12 | { 13 | const string RequestPath = "/test.request.path"; 14 | using var dbContext = new TestDbContext(); 15 | var bus = Substitute.For(); 16 | var publisher = Substitute.For(); 17 | var middleware = new AuditLogsMiddleware(_ => Task.CompletedTask); 18 | var httpContext = Substitute.For(); 19 | httpContext.Request.Path.Returns(PathString.FromUriComponent(new Uri(RequestPath))); 20 | 21 | await middleware.InvokeAsync(httpContext, dbContext, bus, publisher); 22 | 23 | await publisher.Received().ExtractAndPublishAsync(dbContext, bus, RequestPath, Arg.Any()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.AuditLogs.Tests/LeanCode.AuditLogs.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Azure.Tests/LeanCode.Azure.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.AzureIdentity.Tests/AzureIdentityFact.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Test.Helpers; 2 | 3 | namespace LeanCode.AzureIdentity.Tests; 4 | 5 | public sealed class AzureIdentityFact : ExternalServiceFactAttribute 6 | { 7 | protected override string ServiceType => "azure-identity"; 8 | protected override IReadOnlyCollection RequiredEnvVariables { get; } 9 | 10 | public AzureIdentityFact(params string[] requiredEnvVariables) 11 | { 12 | RequiredEnvVariables = requiredEnvVariables.ToHashSet(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.AzureIdentity.Tests/LeanCode.AzureIdentity.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.AzureIdentity.Tests/Properties.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | // This is necessary, because some tests rely on setting environmental variables, which might be overwritten by tests. 4 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true)] 5 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Dapper.Tests/LeanCode.Dapper.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Firebase.FCM.Tests/FCMFact.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Test.Helpers; 2 | 3 | namespace LeanCode.Firebase.FCM.Tests; 4 | 5 | public sealed class FCMFactAttribute : ExternalServiceFactAttribute 6 | { 7 | protected override string ServiceType => "fcm"; 8 | protected override IReadOnlyCollection RequiredEnvVariables { get; } = ["FCM_KEY", "FCM_TOKEN"]; 9 | } 10 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Firebase.FCM.Tests/LeanCode.Firebase.FCM.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ForceUpdate.Tests/LeanCode.ForceUpdate.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | enable 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Kratos.Tests/LeanCode.Kratos.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Localization.Tests/LeanCode.Localization.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Localization.Tests/ResourceManagerStringLocalizerTests.pl.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Zamówienie 5 | 6 | 7 | Zamówienie nr {0} 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Localization.Tests/ResourceManagerStringLocalizerTests.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Order 5 | 6 | 7 | Order no. {0} 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Logging.AspNetCore.Tests/LeanCode.Logging.AspNetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Logging.Tests/LeanCode.Logging.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Mixpanel.Tests/LeanCode.Mixpanel.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.SendGrid.Tests/LeanCode.SendGrid.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.Serialization.Tests/LeanCode.Serialization.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.SmsSender.Tests/LeanCode.SmsSender.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/LeanCode.ViewRenderer.Razor.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | PreserveNewest 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewA.cshtml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewA.cshtml -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewB.cshtml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewB.cshtml -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewC.cshtml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/A/ViewC.cshtml -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewA.cshtml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewA.cshtml -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewB.cstxt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewB.cstxt -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewD.cshtml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leancodepl/corelibrary/12bbe619e6230cd16715d58e1f5210ede9efb72e/test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/B/ViewD.cshtml -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Cache/Simple.cshtml: -------------------------------------------------------------------------------- 1 | this is simple view 2 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/CompiledError.cshtml: -------------------------------------------------------------------------------- 1 | @NonExistingField 2 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/Dotted.Name.cshtml: -------------------------------------------------------------------------------- 1 | Dotted name -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/Layouted.cshtml: -------------------------------------------------------------------------------- 1 | @layout Simple 2 | this is simple view 3 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/LayoutedSecondLine.cshtml: -------------------------------------------------------------------------------- 1 | Test 2 | @layout Simple 3 | this is simple view 4 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/RazorError.cshtml: -------------------------------------------------------------------------------- 1 | @if () { wont } 2 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Compiler/Simple.cshtml: -------------------------------------------------------------------------------- 1 | this is simple view 2 | -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/Functions.cshtml: -------------------------------------------------------------------------------- 1 | @functions { 2 | string Hello() 3 | { 4 | return "Hello"; 5 | } 6 | } 7 | @Hello() -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/HierarchicalLayout.cshtml: -------------------------------------------------------------------------------- 1 | @layout HierarchicalLayout1 2 | CONTENT -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/HierarchicalLayout1.cshtml: -------------------------------------------------------------------------------- 1 | @layout HierarchicalLayout2 2 | HL1-PRE 3 | @await RenderBodyAsync() 4 | HL1-POST -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/HierarchicalLayout2.cshtml: -------------------------------------------------------------------------------- 1 | HL2-PRE 2 | @await RenderBodyAsync() 3 | HL2-POST -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/Layout1.cshtml: -------------------------------------------------------------------------------- 1 | LAYOUT-PRE 2 | @await RenderBodyAsync() 3 | LAYOUT-POST -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/LayoutModel.cshtml: -------------------------------------------------------------------------------- 1 | @layout LayoutModel1 2 | @Model.Child -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/LayoutModel1.cshtml: -------------------------------------------------------------------------------- 1 | PRE-@Model.Layout 2 | @await RenderBodyAsync() 3 | POST-@Model.Layout -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/Layouted.cshtml: -------------------------------------------------------------------------------- 1 | @layout Layout1 2 | CONTENT -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/Model.cshtml: -------------------------------------------------------------------------------- 1 | @Model.Child -------------------------------------------------------------------------------- /test/Infrastructure/LeanCode.ViewRenderer.Razor.Tests/Views/Renderer/Simple.cshtml: -------------------------------------------------------------------------------- 1 | SIMPLE -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/App/AppRoles.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CQRS.Security; 2 | 3 | namespace LeanCode.IntegrationTests.App; 4 | 5 | public class AppRoles : IRoleRegistration 6 | { 7 | public IEnumerable Roles { get; } = [new Role("user", "user")]; 8 | } 9 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/App/AuthConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using LeanCode.IntegrationTestHelpers; 3 | 4 | namespace LeanCode.IntegrationTests.App; 5 | 6 | public static class AuthConfig 7 | { 8 | public static readonly Guid UserId = Guid.NewGuid(); 9 | 10 | public static readonly ClaimsPrincipal User = new( 11 | new ClaimsIdentity( 12 | new Claim[] { new("sub", UserId.ToString()), new("role", "user") }, 13 | TestAuthenticationHandler.SchemeName, 14 | "sub", 15 | "role" 16 | ) 17 | ); 18 | 19 | public static readonly ClaimsPrincipal UserWithoutRole = new( 20 | new ClaimsIdentity( 21 | new Claim[] { new("sub", UserId.ToString()) }, 22 | TestAuthenticationHandler.SchemeName, 23 | "sub", 24 | "role" 25 | ) 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/App/Entity.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using LeanCode.DomainModels.Model; 3 | using LeanCode.TimeProvider; 4 | 5 | namespace LeanCode.IntegrationTests.App; 6 | 7 | public class Entity : IAggregateRootWithoutOptimisticConcurrency 8 | { 9 | public Guid Id { get; set; } 10 | public string Value { get; set; } = null!; 11 | } 12 | 13 | public class EntityAdded : IDomainEvent 14 | { 15 | public Guid Id { get; private init; } 16 | public DateTime DateOccurred { get; private init; } 17 | 18 | public string Value { get; private init; } 19 | public Guid EntityId { get; private init; } 20 | 21 | [JsonConstructor] 22 | public EntityAdded(Guid id, DateTime dateOccurred, string value, Guid entityId) 23 | { 24 | Id = id; 25 | DateOccurred = dateOccurred; 26 | Value = value; 27 | EntityId = entityId; 28 | } 29 | 30 | public EntityAdded(Entity entity) 31 | { 32 | Id = Guid.NewGuid(); 33 | DateOccurred = Time.UtcNow; 34 | 35 | EntityId = entity.Id; 36 | Value = entity.Value; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/App/Meeting.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.DomainModels.Model; 2 | 3 | namespace LeanCode.IntegrationTests.App; 4 | 5 | public class Meeting 6 | { 7 | public Guid Id { get; set; } 8 | public string Name { get; set; } = default!; 9 | public TimestampTz StartTime { get; set; } = default!; 10 | } 11 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/App/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using LeanCode.AzureIdentity; 3 | using LeanCode.Logging.AspNetCore; 4 | using LeanCode.Startup.MicrosoftDI; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace LeanCode.IntegrationTests.App; 8 | 9 | public static class Program 10 | { 11 | [SuppressMessage("?", "IDE0060", Justification = "`args` are required by convention.")] 12 | public static IHostBuilder CreateHostBuilder(string[] args) 13 | { 14 | return LeanProgram 15 | .BuildMinimalHost() 16 | .AddAppConfigurationFromAzureKeyVaultOnNonDevelopmentEnvironment() 17 | .ConfigureDefaultLogging(projectName: "test", destructurers: new[] { typeof(Program).Assembly }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/PostgresOnlyFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Test.Helpers; 2 | 3 | namespace LeanCode.IntegrationTests; 4 | 5 | public sealed class PostgresOnlyFactAttribute : IntegrationFactAttribute 6 | { 7 | public override IReadOnlyCollection> GetTraits() => 8 | base.GetTraits().Append(new("database", "postgres")).ToList(); 9 | } 10 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/docker/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=corelib-integration-tests 2 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:9.0 2 | 3 | WORKDIR /app/code/test/LeanCode.IntegrationTests 4 | ENTRYPOINT dotnet run -- --explicit on 5 | -------------------------------------------------------------------------------- /test/LeanCode.IntegrationTests/docker/Dockerfile.watch: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/sdk:9.0 2 | 3 | # Wait for SQL Server first 4 | WORKDIR /app/code/test/LeanCode.IntegrationTests 5 | ENTRYPOINT dotnet restore && dotnet watch test -- --explicit on 6 | -------------------------------------------------------------------------------- /test/LeanCode.Test.Helpers/IntegrationFactAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Xunit; 3 | using Xunit.v3; 4 | 5 | namespace LeanCode.Test.Helpers; 6 | 7 | [SuppressMessage("?", "CA1813", Justification = "The attribute is inherited by other attributes and cannot be sealed.")] 8 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 9 | public class IntegrationFactAttribute : FactAttribute, ITraitAttribute 10 | { 11 | public IntegrationFactAttribute() 12 | { 13 | Explicit = true; 14 | } 15 | 16 | public virtual IReadOnlyCollection> GetTraits() => 17 | [new("category", "integration"), new("integration", "true")]; 18 | } 19 | -------------------------------------------------------------------------------- /test/LeanCode.Test.Helpers/LeanCode.Test.Helpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | Library 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/LeanCode.Test.Helpers/LongRunningFact.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Xunit.v3; 3 | 4 | namespace LeanCode.Test.Helpers; 5 | 6 | [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 7 | public sealed class LongRunningFact : FactAttribute, ITraitAttribute 8 | { 9 | public IReadOnlyCollection> GetTraits() => [new("category", "long-running")]; 10 | } 11 | 12 | public class LongRunningFactTest 13 | { 14 | [LongRunningFact] 15 | public void Long_running_test() { } 16 | } 17 | -------------------------------------------------------------------------------- /test/LeanCode.TestCoverage.Tests/LeanCode.TestCoverage.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/LeanCode.TestCoverage.Tests/TestCoverageTests.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.TestCoverage.Tests; 2 | 3 | public class TestCoverageTests 4 | { 5 | [Xunit.Fact] 6 | public void Include_all_source_projects_for_test_coverage() { } 7 | } 8 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/AuthQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using LeanCode.Contracts; 3 | using LeanCode.CQRS.Execution; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 7 | 8 | public class AuthQuery : IQuery { } 9 | 10 | public class AuthResult 11 | { 12 | public bool IsAuthenticated { get; set; } 13 | 14 | [SuppressMessage("?", "CA2227", Justification = "Public modifiable list as part of the contract")] 15 | public List> Claims { get; set; } = default!; 16 | } 17 | 18 | public class AuthQueryHandler : IQueryHandler 19 | { 20 | public Task ExecuteAsync(HttpContext context, AuthQuery query) 21 | { 22 | var principal = context.User; 23 | 24 | var result = new AuthResult 25 | { 26 | IsAuthenticated = principal.Identity?.IsAuthenticated ?? false, 27 | Claims = principal.Claims.Select(c => KeyValuePair.Create(c.Type, c.Value)).ToList(), 28 | }; 29 | 30 | return Task.FromResult(result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/Command.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 6 | 7 | public class Command : ICommand 8 | { 9 | public int Id { get; set; } 10 | public string? Data { get; set; } 11 | } 12 | 13 | public class CommandCH : ICommandHandler 14 | { 15 | private readonly TestDbContext dbContext; 16 | 17 | public CommandCH(TestDbContext dbContext) 18 | { 19 | this.dbContext = dbContext; 20 | } 21 | 22 | public Task ExecuteAsync(HttpContext context, Command command) 23 | { 24 | dbContext.Entities.Add(new Entity { Id = command.Id, Data = command.Data }); 25 | return dbContext.SaveChangesAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/ConnectionKeeper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 6 | 7 | public class ConnectionKeeper : IHostedService 8 | { 9 | private readonly AsyncServiceScope scope; 10 | private readonly TestDbContext dbContext; 11 | 12 | public ConnectionKeeper(IServiceProvider serviceProvider) 13 | { 14 | scope = serviceProvider.CreateAsyncScope(); 15 | 16 | dbContext = scope.ServiceProvider.GetRequiredService(); 17 | } 18 | 19 | public Task StartAsync(CancellationToken cancellationToken) => 20 | dbContext.Database.OpenConnectionAsync(cancellationToken); 21 | 22 | public async Task StopAsync(CancellationToken cancellationToken) 23 | { 24 | await dbContext.Database.CloseConnectionAsync(); 25 | await scope.DisposeAsync(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using LeanCode.AzureIdentity; 3 | using LeanCode.Logging.AspNetCore; 4 | using LeanCode.Startup.MicrosoftDI; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 8 | 9 | public static class Program 10 | { 11 | [SuppressMessage("?", "IDE0060", Justification = "`args` are required by convention.")] 12 | public static IHostBuilder CreateHostBuilder(string[] args) 13 | { 14 | return LeanProgram 15 | .BuildMinimalHost() 16 | .AddAppConfigurationFromAzureKeyVaultOnNonDevelopmentEnvironment() 17 | .ConfigureDefaultLogging( 18 | projectName: "integration-tests", 19 | destructurers: new[] { typeof(Program).Assembly } 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/Query.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 7 | 8 | public class Query : IQuery 9 | { 10 | public int Id { get; set; } 11 | } 12 | 13 | public class QueryQH : IQueryHandler 14 | { 15 | private readonly TestDbContext dbContext; 16 | 17 | public QueryQH(TestDbContext dbContext) 18 | { 19 | this.dbContext = dbContext; 20 | } 21 | 22 | public Task ExecuteAsync(HttpContext context, Query query) 23 | { 24 | return dbContext.Entities.Where(e => e.Id == query.Id).Select(e => e.Data).FirstOrDefaultAsync(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/Testing/LeanCode.IntegrationTestHelpers.Tests/App/TestDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace LeanCode.IntegrationTestHelpers.Tests.App; 4 | 5 | public class Entity 6 | { 7 | public int Id { get; set; } 8 | public string? Data { get; set; } 9 | } 10 | 11 | public class TestDbContext : DbContext 12 | { 13 | public DbSet Entities => Set(); 14 | 15 | public TestDbContext(DbContextOptions opts) 16 | : base(opts) { } 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | modelBuilder.Entity(cfg => 21 | { 22 | cfg.HasKey(e => e.Id); 23 | cfg.Property(e => e.Id).ValueGeneratedNever(); 24 | cfg.Property(e => e.Data); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/Analyzers/SuggestCommandsHaveValidatorsTests.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Analyzers; 2 | using LeanCode.CodeAnalysis.Tests.Verifiers; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Xunit; 5 | 6 | namespace LeanCode.CodeAnalysis.Tests.Analyzers; 7 | 8 | public class SuggestCommandsHaveValidatorsTests : DiagnosticVerifier 9 | { 10 | [Fact] 11 | public async Task Ignores_commands_with_validators_reports_not_validated() 12 | { 13 | var source = await File.ReadAllTextAsync("TestSamples/Command_validation.cs"); 14 | var diag = new DiagnosticResult(DiagnosticsIds.CommandsShouldHaveValidators, 18, 13); 15 | await VerifyDiagnostics(source, diag); 16 | } 17 | 18 | protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() 19 | { 20 | return new SuggestCommandsHaveValidators(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/LeanCode.CodeAnalysis.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreserveNewest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/CQRS/Command_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.CQRS; 6 | 7 | public class FirstCommandCH : ICommandHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstCommand command) => throw new NotImplementedException(); 10 | } 11 | 12 | public class MultipleCommandsCH : ICommandHandler, ICommandHandler 13 | { 14 | public Task ExecuteAsync(HttpContext context, FirstCommand command) => throw new NotImplementedException(); 15 | 16 | public Task ExecuteAsync(HttpContext context, SecondCommand command) => throw new NotImplementedException(); 17 | } 18 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/CQRS/Operation_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.CQRS; 6 | 7 | public class FirstOperationOH : IOperationHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstOperation operation) => 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public class MultipleOperationsOH : IOperationHandler, IOperationHandler 14 | { 15 | public Task ExecuteAsync(HttpContext context, FirstOperation operation) => 16 | throw new NotImplementedException(); 17 | 18 | public Task ExecuteAsync(HttpContext context, SecondOperation operation) => 19 | throw new NotImplementedException(); 20 | } 21 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/CQRS/Query_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.CQRS; 6 | 7 | public class FirstQueryQH : IQueryHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstQuery query) => throw new NotImplementedException(); 10 | } 11 | 12 | public class MultipleQueriesQH : IQueryHandler, IQueryHandler 13 | { 14 | public Task ExecuteAsync(HttpContext context, FirstQuery query) => throw new NotImplementedException(); 15 | 16 | public Task ExecuteAsync(HttpContext context, SecondQuery query) => throw new NotImplementedException(); 17 | } 18 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/CQRS/Validators.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using LeanCode.Contracts; 3 | 4 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.CQRS; 5 | 6 | // Roslyn has only context of this file in tests so it's necessary to declare `AcceptedValidatorCommand` here 7 | // to check if it implements `ICommand` interface. 8 | public class ValidatorCommand : ICommand { } 9 | 10 | public class ValidatorOperation : IOperation { } 11 | 12 | public class ValidatorCommandCV : AbstractValidator { } 13 | 14 | // Do not raise diagnostics for classes not implementing `ICommand` interface. 15 | public class Validator : AbstractValidator { } 16 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/Contracts/Commands.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.Contracts.Security; 3 | 4 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 5 | 6 | public abstract class BaseCommand : ICommand { } 7 | 8 | [AuthorizeWhenHasAnyOf("Some_permission")] 9 | public class FirstCommand : BaseCommand { } 10 | 11 | public class SecondCommand : FirstCommand { } 12 | 13 | [AuthorizeWhenCustom] 14 | public class ThirdCommand : ICommand { } 15 | 16 | [AllowUnauthorized] 17 | public class UnauthorizedCommand : ICommand { } 18 | 19 | public sealed class AuthorizeWhenCustomAttribute : AuthorizeWhenAttribute 20 | { 21 | public AuthorizeWhenCustomAttribute(Type authorizerType = null) 22 | : base(authorizerType ?? typeof(object)) { } 23 | } 24 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/Contracts/Operations.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | using LeanCode.Contracts.Security; 3 | 4 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 5 | 6 | public abstract class BaseOperation : IOperation { } 7 | 8 | [AuthorizeWhenHasAnyOf("Sample_permission")] 9 | public class FirstOperation : BaseOperation { } 10 | 11 | public class SecondOperation : FirstOperation { } 12 | 13 | [AuthorizeWhenHasAnyOf("Sample_permission")] 14 | public class ThirdOperation : IOperation { } 15 | 16 | [AllowUnauthorized] 17 | public class UnauthorizedOperation : IOperation { } 18 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/Contracts/Queries.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using LeanCode.Contracts; 3 | using LeanCode.Contracts.Security; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 6 | 7 | public abstract class BaseQuery : IQuery { } 8 | 9 | [AuthorizeWhenHasAnyOf("Sample_permission")] 10 | public class FirstQuery : BaseQuery { } 11 | 12 | public class SecondQuery : FirstQuery { } 13 | 14 | [AuthorizeWhenHasAnyOf("Sample_permission")] 15 | public class ThirdQuery : IQuery { } 16 | 17 | [SuppressMessage("??", "CA1040", Justification = "Empty marker interface")] 18 | public interface ICustomQueryAuthorizer { } 19 | 20 | public sealed class CustomQueryAuthorizerAttribute : AuthorizeWhenAttribute { } 21 | 22 | [CustomQueryAuthorizer] 23 | public class CustomAuthorizedQuery : IQuery { } 24 | 25 | [AllowUnauthorized] 26 | public class UnauthorizedQuery : IQuery { } 27 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Accepted/Methods.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Accepted; 2 | 3 | public static class Methods 4 | { 5 | public static CancellationToken CancellationTokenNamingConvention(CancellationToken cancellationToken) 6 | { 7 | return cancellationToken; 8 | } 9 | } 10 | 11 | // Methods with `override` and `new` keywords are accepted as they might come from external source 12 | public class OverrideMethod : Rejected.Methods 13 | { 14 | public override CancellationToken CancellationTokenNamingConvention(CancellationToken ct) 15 | { 16 | return ct; 17 | } 18 | } 19 | 20 | public class AcceptedNewMethod : Rejected.Methods 21 | { 22 | public static new CancellationToken CancellationTokenNamingConvention(CancellationToken ct) 23 | { 24 | return ct; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Command_validation.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using LeanCode.Contracts; 3 | using LeanCode.CQRS.Execution; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace LeanCode.CodeAnalysis.Tests.TestSamples; 7 | 8 | public class ValidatedCommand : ICommand { } 9 | 10 | public class Validator : AbstractValidator { } 11 | 12 | public class ValidatedHandler : ICommandHandler 13 | { 14 | public Task ExecuteAsync(HttpContext context, ValidatedCommand command) => Task.CompletedTask; 15 | } 16 | 17 | public class NotValidatedCommand : ICommand { } 18 | 19 | public class NotValidatedHandler : ICommandHandler 20 | { 21 | public Task ExecuteAsync(HttpContext context, NotValidatedCommand command) => Task.CompletedTask; 22 | } 23 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/CQRS/Command_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.CQRS.WrongNamespace; 6 | 7 | public class WrongCommandHandlerName : ICommandHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstCommand command) => throw new NotImplementedException(); 10 | } 11 | 12 | public class WrongMultipleCommandHandlerName : ICommandHandler, ICommandHandler 13 | { 14 | public Task ExecuteAsync(HttpContext context, FirstCommand command) => throw new NotImplementedException(); 15 | 16 | public Task ExecuteAsync(HttpContext context, SecondCommand command) => throw new NotImplementedException(); 17 | } 18 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/CQRS/Operation_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.CQRS.WrongNamespace; 6 | 7 | public class WrongOperationHandlerName : IOperationHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstOperation operation) => 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public class WrongMultipleOperationHandlerName 14 | : IOperationHandler, 15 | IOperationHandler 16 | { 17 | public Task ExecuteAsync(HttpContext context, FirstOperation operation) => 18 | throw new NotImplementedException(); 19 | 20 | public Task ExecuteAsync(HttpContext context, SecondOperation operation) => 21 | throw new NotImplementedException(); 22 | } 23 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/CQRS/Query_handlers.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.CodeAnalysis.Tests.TestSamples.Accepted.Contracts; 2 | using LeanCode.CQRS.Execution; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.CQRS.WrongNamespace; 6 | 7 | public class WrongQueryHandlerName : IQueryHandler 8 | { 9 | public Task ExecuteAsync(HttpContext context, FirstQuery query) => throw new NotImplementedException(); 10 | } 11 | 12 | public class WrongMultipleQueryHandlerName : IQueryHandler, IQueryHandler 13 | { 14 | public Task ExecuteAsync(HttpContext context, FirstQuery query) => throw new NotImplementedException(); 15 | 16 | public Task ExecuteAsync(HttpContext context, SecondQuery query) => throw new NotImplementedException(); 17 | } 18 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/CQRS/Validators.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | using LeanCode.Contracts; 3 | 4 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.CQRS; 5 | 6 | // Roslyn has only context of this file in tests so it's necessary to declare `RejectedValidatorCommand` here 7 | // to check if it implements `ICommand` interface. 8 | public class ValidatorCommand : ICommand { } 9 | 10 | public class WrongName : AbstractValidator { } 11 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/Contracts/Commands.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.Contracts; 4 | 5 | public class FirstCommand : ICommand { } 6 | 7 | public class SecondCommand : FirstCommand { } 8 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/Contracts/Operations.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.Contracts; 4 | 5 | public class FirstOperation : IOperation { } 6 | 7 | public class SecondOperation : FirstOperation { } 8 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/Contracts/Queries.cs: -------------------------------------------------------------------------------- 1 | using LeanCode.Contracts; 2 | 3 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected.Contracts; 4 | 5 | public class FirstQuery : IQuery { } 6 | 7 | public class SecondQuery : FirstQuery { } 8 | -------------------------------------------------------------------------------- /test/Tools/LeanCode.CodeAnalysis.Tests/TestSamples/Rejected/Methods.cs: -------------------------------------------------------------------------------- 1 | namespace LeanCode.CodeAnalysis.Tests.TestSamples.Rejected; 2 | 3 | public class Methods 4 | { 5 | public virtual CancellationToken CancellationTokenNamingConvention(CancellationToken ct) 6 | { 7 | return ct; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ^System\.ObsoleteAttribute$ 7 | ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ 8 | ^System\.Runtime\.CompilerServices\.CompilerGeneratedAttribute$ 9 | ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------