mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-05-02 19:02:55 +08:00
feat: update dotnet to version 9
This commit is contained in:
parent
42c300fd96
commit
4785a680da
@ -10,11 +10,11 @@
|
|||||||
"rollForward": false
|
"rollForward": false
|
||||||
},
|
},
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "8.0.8",
|
"version": "9.0.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
],
|
],
|
||||||
"rollForward": false
|
"rollForward": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,6 +241,11 @@ dotnet_diagnostic.IDE0055.severity = suggestion
|
|||||||
# CS1574: XML comment on 'construct' has syntactically incorrect cref attribute 'name'
|
# CS1574: XML comment on 'construct' has syntactically incorrect cref attribute 'name'
|
||||||
dotnet_diagnostic.CS1574.severity = error
|
dotnet_diagnostic.CS1574.severity = error
|
||||||
|
|
||||||
|
# IDE0160, IDE0161: Report violations when block-scoped namespaces are used
|
||||||
|
dotnet_diagnostic.IDE0160.severity = none
|
||||||
|
dotnet_diagnostic.IDE0161.severity = none
|
||||||
|
|
||||||
|
|
||||||
##################################################################################
|
##################################################################################
|
||||||
# https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html
|
# https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html
|
||||||
# https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html
|
# https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@ -10,22 +10,22 @@
|
|||||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" Version="1.1.118">
|
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" Version="1.1.118">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" Version="2.0.163">
|
<PackageReference Include="Meziantou.Analyzer" PrivateAssets="all" Version="2.0.182">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" Version="4.12.5">
|
<PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" PrivateAssets="all" Version="4.12.5">
|
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Roslynator.Formatting.Analyzers" PrivateAssets="all" Version="4.12.5">
|
<PackageReference Include="Roslynator.Formatting.Analyzers" PrivateAssets="all" Version="4.12.9">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all" Version="17.11.20">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all" Version="17.12.19">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="8.0.0">
|
<PackageReference Include="AsyncAwaitBestPractices" PrivateAssets="all" Version="9.0.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="all" Version="0.15.0">
|
<PackageReference Include="SerilogAnalyzer" PrivateAssets="all" Version="0.15.0">
|
||||||
|
|||||||
25
README.md
25
README.md
@ -55,6 +55,7 @@
|
|||||||
- :sparkle: Using `End-To-End Testing` and `Integration Testing` for testing `features` with all dependencies using `testcontainers`.
|
- :sparkle: Using `End-To-End Testing` and `Integration Testing` for testing `features` with all dependencies using `testcontainers`.
|
||||||
- :sparkle: Using `Fluent Validation` and a `Validation Pipeline Behaviour` on top of `MediatR`.
|
- :sparkle: Using `Fluent Validation` and a `Validation Pipeline Behaviour` on top of `MediatR`.
|
||||||
- :sparkle: Using `Minimal API` for all endpoints.
|
- :sparkle: Using `Minimal API` for all endpoints.
|
||||||
|
- :sparkle: Using `AspNetCore OpenApi` for `generating` built-in support `OpenAPI documentation` in ASP.NET Core.
|
||||||
- :sparkle: Using `Health Check` for `reporting` the `health` of app infrastructure components.
|
- :sparkle: Using `Health Check` for `reporting` the `health` of app infrastructure components.
|
||||||
- :sparkle: Using `Docker-Compose` and `Kubernetes` for our deployment mechanism.
|
- :sparkle: Using `Docker-Compose` and `Kubernetes` for our deployment mechanism.
|
||||||
- :sparkle: Using `Kibana` on top of `Serilog` for `logging`.
|
- :sparkle: Using `Kibana` on top of `Serilog` for `logging`.
|
||||||
@ -85,24 +86,26 @@ High-level plan is represented in the table
|
|||||||
|
|
||||||
## Technologies - Libraries
|
## Technologies - Libraries
|
||||||
|
|
||||||
- ✔️ **[`.NET 7`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core
|
- ✔️ **[`.NET 7`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core.
|
||||||
- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core
|
- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core.
|
||||||
- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations
|
- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
|
||||||
|
- ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&tabs=visual-studio#configure-openapi-document-generation)** - Provides built-in support for OpenAPI document generation in ASP.NET Core.
|
||||||
- ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET.
|
- ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET.
|
||||||
- ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .NET.
|
- ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .NET.
|
||||||
- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules
|
- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules.
|
||||||
- ✔️ **[`Swagger & Swagger UI`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)** - Swagger tools for documenting API's built on ASP.NET Core
|
- ✔️ **[`Scalar`](https://github.com/scalar/scalar/tree/main/packages/scalar.aspnetcore)** - Scalar provides an easy way to render beautiful API references based on OpenAPI/Swagger documents.
|
||||||
|
- ✔️ **[`Swagger UI`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)** - Swagger tools for documenting API's built on ASP.NET Core.
|
||||||
- ✔️ **[`Serilog`](https://github.com/serilog/serilog)** - Simple .NET logging with fully-structured events
|
- ✔️ **[`Serilog`](https://github.com/serilog/serilog)** - Simple .NET logging with fully-structured events
|
||||||
- ✔️ **[`Polly`](https://github.com/App-vNext/Polly)** - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner
|
- ✔️ **[`Polly`](https://github.com/App-vNext/Polly)** - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.
|
||||||
- ✔️ **[`Scrutor`](https://github.com/khellang/Scrutor)** - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
|
- ✔️ **[`Scrutor`](https://github.com/khellang/Scrutor)** - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
|
||||||
- ✔️ **[`Opentelemetry-dotnet`](https://github.com/open-telemetry/opentelemetry-dotnet)** - The OpenTelemetry .NET Client
|
- ✔️ **[`Opentelemetry-dotnet`](https://github.com/open-telemetry/opentelemetry-dotnet)** - The OpenTelemetry .NET Client
|
||||||
- ✔️ **[`DuendeSoftware IdentityServer`](https://github.com/DuendeSoftware/IdentityServer)** - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core
|
- ✔️ **[`DuendeSoftware IdentityServer`](https://github.com/DuendeSoftware/IdentityServer)** - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core.
|
||||||
- ✔️ **[`EasyCaching`](https://github.com/dotnetcore/EasyCaching)** - Open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier.
|
- ✔️ **[`EasyCaching`](https://github.com/dotnetcore/EasyCaching)** - Open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier.
|
||||||
- ✔️ **[`Mapster`](https://github.com/MapsterMapper/Mapster)** - Convention-based object-object mapper in .NET.
|
- ✔️ **[`Mapster`](https://github.com/MapsterMapper/Mapster)** - Convention-based object-object mapper in .NET.
|
||||||
- ✔️ **[`Hellang.Middleware.ProblemDetails`](https://github.com/khellang/Middleware/tree/master/src/ProblemDetails)** - A middleware for handling exception in .Net Core
|
- ✔️ **[`Hellang.Middleware.ProblemDetails`](https://github.com/khellang/Middleware/tree/master/src/ProblemDetails)** - A middleware for handling exception in .Net Core.
|
||||||
- ✔️ **[`NewId`](https://github.com/phatboyg/NewId)** - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs
|
- ✔️ **[`NewId`](https://github.com/phatboyg/NewId)** - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs.
|
||||||
- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET
|
- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET.
|
||||||
- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier
|
- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier.
|
||||||
- ✔️ **[`gRPC-dotnet`](https://github.com/grpc/grpc-dotnet)** - gRPC functionality for .NET.
|
- ✔️ **[`gRPC-dotnet`](https://github.com/grpc/grpc-dotnet)** - gRPC functionality for .NET.
|
||||||
- ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing.
|
- ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing.
|
||||||
- ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB.
|
- ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.401",
|
"version": "9.0.100",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Ardalis.GuardClauses" Version="4.6.0" />
|
<PackageReference Include="Ardalis.GuardClauses" Version="5.0.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Abstractions" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Abstractions" Version="8.1.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Http" Version="8.1.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||||
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="8.0.2" />
|
<PackageReference Include="AspNetCore.HealthChecks.UI" Version="8.0.2" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
|
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
|
||||||
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="8.0.1" />
|
<PackageReference Include="AspNetCore.HealthChecks.UI.InMemory.Storage" Version="8.0.1" />
|
||||||
@ -19,41 +20,42 @@
|
|||||||
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
<PackageReference Include="EasyCaching.Core" Version="1.9.2" />
|
||||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||||
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.0" />
|
<PackageReference Include="EasyNetQ.Management.Client" Version="3.0.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Figgle" Version="0.5.1" />
|
<PackageReference Include="Figgle" Version="0.5.1" />
|
||||||
<PackageReference Include="FluentValidation" Version="11.10.0" />
|
<PackageReference Include="FluentValidation" Version="11.11.0" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
|
||||||
<PackageReference Include="Npgsql" Version="8.0.4" />
|
<PackageReference Include="Npgsql" Version="9.0.1" />
|
||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-beta.2" />
|
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.10.0-beta.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.9.0-beta.1" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.9.0-beta.1" />
|
||||||
<PackageReference Include="Polly" Version="8.4.1" />
|
<PackageReference Include="Polly" Version="8.5.0" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="IdGen" Version="3.0.7" />
|
<PackageReference Include="IdGen" Version="3.0.7" />
|
||||||
<PackageReference Include="Mapster" Version="7.4.0" />
|
<PackageReference Include="Mapster" Version="7.4.0" />
|
||||||
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
|
||||||
<PackageReference Include="MongoDB.Driver" Version="2.28.0" />
|
<PackageReference Include="MongoDB.Driver" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="OpenTelemetry.Contrib.Instrumentation.MassTransit" Version="1.0.0-beta2" />
|
<PackageReference Include="OpenTelemetry.Contrib.Instrumentation.MassTransit" Version="1.0.0-beta2" />
|
||||||
<PackageReference Include="Scrutor" Version="4.2.2" />
|
<PackageReference Include="Scalar.AspNetCore" Version="1.2.64" />
|
||||||
<PackageReference Include="Sentry.Serilog" Version="4.10.2" />
|
<PackageReference Include="Scrutor" Version="5.0.2" />
|
||||||
<PackageReference Include="Serilog" Version="4.0.1" />
|
<PackageReference Include="Sentry.Serilog" Version="4.13.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
<PackageReference Include="Serilog.Enrichers.Span" Version="3.1.0" />
|
||||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||||
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="10.0.0" />
|
<PackageReference Include="Serilog.Formatting.Elasticsearch" Version="10.0.0" />
|
||||||
@ -63,48 +65,53 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.3.3" />
|
<PackageReference Include="Serilog.Sinks.SpectreConsole" Version="0.3.3" />
|
||||||
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.5" />
|
<PackageReference Include="Serilog.Sinks.XUnit" Version="3.0.5" />
|
||||||
<PackageReference Include="Sieve" Version="2.5.5" />
|
<PackageReference Include="Sieve" Version="2.5.5" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.1.0" />
|
||||||
<PackageReference Include="MassTransit" Version="8.2.5" />
|
<PackageReference Include="MassTransit" Version="8.3.3" />
|
||||||
<PackageReference Include="MassTransit.RabbitMQ" Version="8.2.5" />
|
<PackageReference Include="MassTransit.RabbitMQ" Version="8.3.3" />
|
||||||
<PackageReference Include="Duende.IdentityServer" Version="7.0.6" />
|
<PackageReference Include="Duende.IdentityServer" Version="7.0.8" />
|
||||||
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.0.6" />
|
<PackageReference Include="Duende.IdentityServer.AspNetIdentity" Version="7.0.8" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.0.6" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework" Version="7.0.8" />
|
||||||
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.0.6" />
|
<PackageReference Include="Duende.IdentityServer.EntityFramework.Storage" Version="7.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
|
||||||
<PackageReference Include="Testcontainers" Version="3.10.0" />
|
<PackageReference Include="Testcontainers" Version="4.0.0" />
|
||||||
<PackageReference Include="Testcontainers.EventStoreDb" Version="3.10.0" />
|
<PackageReference Include="Testcontainers.EventStoreDb" Version="4.0.0" />
|
||||||
<PackageReference Include="Testcontainers.MongoDb" Version="3.10.0" />
|
<PackageReference Include="Testcontainers.MongoDb" Version="4.0.0" />
|
||||||
<PackageReference Include="Testcontainers.PostgreSql" Version="3.10.0" />
|
<PackageReference Include="Testcontainers.PostgreSql" Version="4.0.0" />
|
||||||
<PackageReference Include="Testcontainers.RabbitMq" Version="3.10.0" />
|
<PackageReference Include="Testcontainers.RabbitMq" Version="4.0.0" />
|
||||||
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />
|
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />
|
||||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
|
<PackageReference Include="Yarp.ReverseProxy" Version="2.2.0" />
|
||||||
|
|
||||||
<PackageReference Include="prometheus-net" Version="8.2.1" />
|
<PackageReference Include="prometheus-net" Version="8.2.1" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
|
|
||||||
<PackageReference Include="OpenTelemetry" Version="1.9.0" />
|
<PackageReference Include="OpenTelemetry" Version="1.10.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.6.0-rc.1" />
|
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.6.0-rc.1" />
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
|
||||||
|
|
||||||
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.5" />
|
<PackageReference Include="EventStore.Client.Grpc.Streams" Version="23.3.7" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
||||||
|
|
||||||
<PackageReference Include="AutoBogus" Version="2.13.1" />
|
<PackageReference Include="AutoBogus" Version="2.13.1" />
|
||||||
<PackageReference Include="Bogus" Version="35.6.1" />
|
<PackageReference Include="Bogus" Version="35.6.1" />
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||||
<PackageReference Include="Respawn" Version="6.2.1" />
|
<PackageReference Include="Respawn" Version="6.2.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
|
||||||
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="8.0.1" />
|
<PackageReference Include="WebMotions.Fake.Authentication.JwtBearer" Version="8.0.1" />
|
||||||
|
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.28.1" />
|
<PackageReference Include="Google.Protobuf" Version="3.29.1" />
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.65.0" />
|
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.67.0" />
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -9,30 +9,17 @@ using MessageEnvelope = BuildingBlocks.Core.Event.MessageEnvelope;
|
|||||||
|
|
||||||
namespace BuildingBlocks.Core;
|
namespace BuildingBlocks.Core;
|
||||||
|
|
||||||
public sealed class EventDispatcher : IEventDispatcher
|
public sealed class EventDispatcher(
|
||||||
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
IEventMapper eventMapper,
|
||||||
|
ILogger<EventDispatcher> logger,
|
||||||
|
IPersistMessageProcessor persistMessageProcessor,
|
||||||
|
IHttpContextAccessor httpContextAccessor
|
||||||
|
)
|
||||||
|
: IEventDispatcher
|
||||||
{
|
{
|
||||||
private readonly IEventMapper _eventMapper;
|
|
||||||
private readonly ILogger<EventDispatcher> _logger;
|
|
||||||
private readonly IPersistMessageProcessor _persistMessageProcessor;
|
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
|
||||||
|
|
||||||
public EventDispatcher(IServiceScopeFactory serviceScopeFactory,
|
|
||||||
IEventMapper eventMapper,
|
|
||||||
ILogger<EventDispatcher> logger,
|
|
||||||
IPersistMessageProcessor persistMessageProcessor,
|
|
||||||
IHttpContextAccessor httpContextAccessor)
|
|
||||||
{
|
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
|
||||||
_eventMapper = eventMapper;
|
|
||||||
_logger = logger;
|
|
||||||
_persistMessageProcessor = persistMessageProcessor;
|
|
||||||
_httpContextAccessor = httpContextAccessor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task SendAsync<T>(IReadOnlyList<T> events, Type type = null,
|
public async Task SendAsync<T>(IReadOnlyList<T> events, Type type = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
where T : IEvent
|
where T : IEvent
|
||||||
{
|
{
|
||||||
if (events.Count > 0)
|
if (events.Count > 0)
|
||||||
@ -45,7 +32,7 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
{
|
{
|
||||||
foreach (var integrationEvent in integrationEvents)
|
foreach (var integrationEvent in integrationEvents)
|
||||||
{
|
{
|
||||||
await _persistMessageProcessor.PublishMessageAsync(
|
await persistMessageProcessor.PublishMessageAsync(
|
||||||
new MessageEnvelope(integrationEvent, SetHeaders()),
|
new MessageEnvelope(integrationEvent, SetHeaders()),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
}
|
||||||
@ -74,7 +61,7 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
|
|
||||||
foreach (var internalMessage in internalMessages)
|
foreach (var internalMessage in internalMessages)
|
||||||
{
|
{
|
||||||
await _persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken);
|
await persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,20 +76,20 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
private Task<IReadOnlyList<IIntegrationEvent>> MapDomainEventToIntegrationEventAsync(
|
private Task<IReadOnlyList<IIntegrationEvent>> MapDomainEventToIntegrationEventAsync(
|
||||||
IReadOnlyList<IDomainEvent> events)
|
IReadOnlyList<IDomainEvent> events)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Processing integration events start...");
|
logger.LogTrace("Processing integration events start...");
|
||||||
|
|
||||||
var wrappedIntegrationEvents = GetWrappedIntegrationEvents(events.ToList())?.ToList();
|
var wrappedIntegrationEvents = GetWrappedIntegrationEvents(events.ToList())?.ToList();
|
||||||
if (wrappedIntegrationEvents?.Count > 0)
|
if (wrappedIntegrationEvents?.Count > 0)
|
||||||
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(wrappedIntegrationEvents);
|
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(wrappedIntegrationEvents);
|
||||||
|
|
||||||
var integrationEvents = new List<IIntegrationEvent>();
|
var integrationEvents = new List<IIntegrationEvent>();
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
foreach (var @event in events)
|
foreach (var @event in events)
|
||||||
{
|
{
|
||||||
var eventType = @event.GetType();
|
var eventType = @event.GetType();
|
||||||
_logger.LogTrace($"Handling domain event: {eventType.Name}");
|
logger.LogTrace($"Handling domain event: {eventType.Name}");
|
||||||
|
|
||||||
var integrationEvent = _eventMapper.MapToIntegrationEvent(@event);
|
var integrationEvent = eventMapper.MapToIntegrationEvent(@event);
|
||||||
|
|
||||||
if (integrationEvent is null)
|
if (integrationEvent is null)
|
||||||
continue;
|
continue;
|
||||||
@ -110,7 +97,7 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
integrationEvents.Add(integrationEvent);
|
integrationEvents.Add(integrationEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogTrace("Processing integration events done...");
|
logger.LogTrace("Processing integration events done...");
|
||||||
|
|
||||||
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(integrationEvents);
|
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(integrationEvents);
|
||||||
}
|
}
|
||||||
@ -119,16 +106,16 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
private Task<IReadOnlyList<IInternalCommand>> MapDomainEventToInternalCommandAsync(
|
private Task<IReadOnlyList<IInternalCommand>> MapDomainEventToInternalCommandAsync(
|
||||||
IReadOnlyList<IDomainEvent> events)
|
IReadOnlyList<IDomainEvent> events)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Processing internal message start...");
|
logger.LogTrace("Processing internal message start...");
|
||||||
|
|
||||||
var internalCommands = new List<IInternalCommand>();
|
var internalCommands = new List<IInternalCommand>();
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = serviceScopeFactory.CreateScope();
|
||||||
foreach (var @event in events)
|
foreach (var @event in events)
|
||||||
{
|
{
|
||||||
var eventType = @event.GetType();
|
var eventType = @event.GetType();
|
||||||
_logger.LogTrace($"Handling domain event: {eventType.Name}");
|
logger.LogTrace($"Handling domain event: {eventType.Name}");
|
||||||
|
|
||||||
var integrationEvent = _eventMapper.MapToInternalCommand(@event);
|
var integrationEvent = eventMapper.MapToInternalCommand(@event);
|
||||||
|
|
||||||
if (integrationEvent is null)
|
if (integrationEvent is null)
|
||||||
continue;
|
continue;
|
||||||
@ -136,7 +123,7 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
internalCommands.Add(integrationEvent);
|
internalCommands.Add(integrationEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogTrace("Processing internal message done...");
|
logger.LogTrace("Processing internal message done...");
|
||||||
|
|
||||||
return Task.FromResult<IReadOnlyList<IInternalCommand>>(internalCommands);
|
return Task.FromResult<IReadOnlyList<IInternalCommand>>(internalCommands);
|
||||||
}
|
}
|
||||||
@ -159,9 +146,9 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
private IDictionary<string, object> SetHeaders()
|
private IDictionary<string, object> SetHeaders()
|
||||||
{
|
{
|
||||||
var headers = new Dictionary<string, object>();
|
var headers = new Dictionary<string, object>();
|
||||||
headers.Add("CorrelationId", _httpContextAccessor?.HttpContext?.GetCorrelationId());
|
headers.Add("CorrelationId", httpContextAccessor?.HttpContext?.GetCorrelationId());
|
||||||
headers.Add("UserId", _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier));
|
headers.Add("UserId", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
headers.Add("UserName", _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.Name));
|
headers.Add("UserName", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.Name));
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
namespace BuildingBlocks.EFCore;
|
|
||||||
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Core.Event;
|
using BuildingBlocks.Core.Event;
|
||||||
using Core.Model;
|
using BuildingBlocks.Core.Model;
|
||||||
|
using BuildingBlocks.Web;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Web;
|
|
||||||
using Exception = System.Exception;
|
|
||||||
using IsolationLevel = System.Data.IsolationLevel;
|
using IsolationLevel = System.Data.IsolationLevel;
|
||||||
|
|
||||||
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
public abstract class AppDbContextBase : DbContext, IDbContext
|
public abstract class AppDbContextBase : DbContext, IDbContext
|
||||||
{
|
{
|
||||||
private readonly ICurrentUserProvider? _currentUserProvider;
|
private readonly ICurrentUserProvider? _currentUserProvider;
|
||||||
@ -174,9 +173,9 @@ public abstract class AppDbContextBase : DbContext, IDbContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
throw new Exception("try for find IAggregate", ex);
|
throw new System.Exception("try for find IAggregate", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Design;
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|||||||
@ -2,49 +2,38 @@ using System.Text.Json;
|
|||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Transactions;
|
||||||
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
|
using BuildingBlocks.Polly;
|
||||||
|
|
||||||
namespace BuildingBlocks.EFCore;
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
using System.Transactions;
|
|
||||||
using PersistMessageProcessor;
|
|
||||||
using Polly;
|
|
||||||
|
|
||||||
public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
public class EfTxBehavior<TRequest, TResponse>(
|
||||||
where TRequest : notnull, IRequest<TResponse>
|
ILogger<EfTxBehavior<TRequest, TResponse>> logger,
|
||||||
where TResponse : notnull
|
IDbContext dbContextBase,
|
||||||
|
IPersistMessageDbContext persistMessageDbContext,
|
||||||
|
IEventDispatcher eventDispatcher
|
||||||
|
)
|
||||||
|
: IPipelineBehavior<TRequest, TResponse>
|
||||||
|
where TRequest : notnull, IRequest<TResponse>
|
||||||
|
where TResponse : notnull
|
||||||
{
|
{
|
||||||
private readonly ILogger<EfTxBehavior<TRequest, TResponse>> _logger;
|
|
||||||
private readonly IDbContext _dbContextBase;
|
|
||||||
private readonly IPersistMessageDbContext _persistMessageDbContext;
|
|
||||||
private readonly IEventDispatcher _eventDispatcher;
|
|
||||||
|
|
||||||
public EfTxBehavior(
|
|
||||||
ILogger<EfTxBehavior<TRequest, TResponse>> logger,
|
|
||||||
IDbContext dbContextBase,
|
|
||||||
IPersistMessageDbContext persistMessageDbContext,
|
|
||||||
IEventDispatcher eventDispatcher)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_dbContextBase = dbContextBase;
|
|
||||||
_persistMessageDbContext = persistMessageDbContext;
|
|
||||||
_eventDispatcher = eventDispatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
|
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
logger.LogInformation(
|
||||||
"{Prefix} Handled command {MediatrRequest}",
|
"{Prefix} Handled command {MediatrRequest}",
|
||||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||||
typeof(TRequest).FullName);
|
typeof(TRequest).FullName);
|
||||||
|
|
||||||
_logger.LogDebug(
|
logger.LogDebug(
|
||||||
"{Prefix} Handled command {MediatrRequest} with content {RequestContent}",
|
"{Prefix} Handled command {MediatrRequest} with content {RequestContent}",
|
||||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||||
typeof(TRequest).FullName,
|
typeof(TRequest).FullName,
|
||||||
JsonSerializer.Serialize(request));
|
JsonSerializer.Serialize(request));
|
||||||
|
|
||||||
_logger.LogInformation(
|
logger.LogInformation(
|
||||||
"{Prefix} Open the transaction for {MediatrRequest}",
|
"{Prefix} Open the transaction for {MediatrRequest}",
|
||||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||||
typeof(TRequest).FullName);
|
typeof(TRequest).FullName);
|
||||||
@ -56,32 +45,32 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
|
|||||||
|
|
||||||
var response = await next();
|
var response = await next();
|
||||||
|
|
||||||
_logger.LogInformation(
|
logger.LogInformation(
|
||||||
"{Prefix} Executed the {MediatrRequest} request",
|
"{Prefix} Executed the {MediatrRequest} request",
|
||||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||||
typeof(TRequest).FullName);
|
typeof(TRequest).FullName);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var domainEvents = _dbContextBase.GetDomainEvents();
|
var domainEvents = dbContextBase.GetDomainEvents();
|
||||||
|
|
||||||
if (domainEvents is null || !domainEvents.Any())
|
if (domainEvents is null || !domainEvents.Any())
|
||||||
{
|
{
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken);
|
await eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken);
|
||||||
|
|
||||||
// Save data to database with some retry policy in distributed transaction
|
// Save data to database with some retry policy in distributed transaction
|
||||||
await _dbContextBase.RetryOnFailure(async () =>
|
await dbContextBase.RetryOnFailure(async () =>
|
||||||
{
|
{
|
||||||
await _dbContextBase.SaveChangesAsync(cancellationToken);
|
await dbContextBase.SaveChangesAsync(cancellationToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save data to database with some retry policy in distributed transaction
|
// Save data to database with some retry policy in distributed transaction
|
||||||
await _persistMessageDbContext.RetryOnFailure(async () =>
|
await persistMessageDbContext.RetryOnFailure(async () =>
|
||||||
{
|
{
|
||||||
await _persistMessageDbContext.SaveChangesAsync(cancellationToken);
|
await persistMessageDbContext.SaveChangesAsync(cancellationToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.Complete();
|
scope.Complete();
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
using BuildingBlocks.Core.Model;
|
using BuildingBlocks.Core.Model;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
|
using Humanizer;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Query;
|
using Microsoft.EntityFrameworkCore.Query;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BuildingBlocks.EFCore;
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
using Ardalis.GuardClauses;
|
|
||||||
using Humanizer;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddCustomDbContext<TContext>(this IServiceCollection services)
|
public static IServiceCollection AddCustomDbContext<TContext>(this IServiceCollection services)
|
||||||
@ -36,32 +37,30 @@ public static class Extensions
|
|||||||
{
|
{
|
||||||
dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name);
|
dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name);
|
||||||
})
|
})
|
||||||
// https://github.com/efcore/EFCore.NamingConventions
|
|
||||||
.UseSnakeCaseNamingConvention();
|
.UseSnakeCaseNamingConvention();
|
||||||
|
|
||||||
|
// Suppress warnings for pending model changes
|
||||||
|
options.ConfigureWarnings(
|
||||||
|
w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddScoped<IDbContext>(provider => provider.GetService<TContext>());
|
services.AddScoped<ISeedManager, SeedManager>();
|
||||||
|
services.AddScoped<IDbContext>(sp => sp.GetRequiredService<TContext>());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IApplicationBuilder UseMigration<TContext>(
|
|
||||||
this IApplicationBuilder app,
|
public static IApplicationBuilder UseMigration<TContext>(this IApplicationBuilder app)
|
||||||
IWebHostEnvironment env
|
|
||||||
)
|
|
||||||
where TContext : DbContext, IDbContext
|
where TContext : DbContext, IDbContext
|
||||||
{
|
{
|
||||||
MigrateDatabaseAsync<TContext>(app.ApplicationServices).GetAwaiter().GetResult();
|
MigrateAsync<TContext>(app.ApplicationServices).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (!env.IsEnvironment("test"))
|
SeedAsync(app.ApplicationServices).GetAwaiter().GetResult();
|
||||||
{
|
|
||||||
SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs
|
// ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs
|
||||||
public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder)
|
public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -114,23 +113,30 @@ public static class Extensions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task MigrateDatabaseAsync<TContext>(IServiceProvider serviceProvider)
|
private static async Task MigrateAsync<TContext>(IServiceProvider serviceProvider)
|
||||||
where TContext : DbContext, IDbContext
|
where TContext : DbContext, IDbContext
|
||||||
{
|
{
|
||||||
using var scope = serviceProvider.CreateScope();
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
|
|
||||||
var context = scope.ServiceProvider.GetRequiredService<TContext>();
|
var context = scope.ServiceProvider.GetRequiredService<TContext>();
|
||||||
await context.Database.MigrateAsync();
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<TContext>>();
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task SeedDataAsync(IServiceProvider serviceProvider)
|
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
|
||||||
{
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
|
||||||
|
|
||||||
foreach (var seeder in seeders)
|
if (pendingMigrations.Any())
|
||||||
{
|
{
|
||||||
await seeder.SeedAllAsync();
|
logger.LogInformation("Applying {Count} pending migrations...", pendingMigrations.Count());
|
||||||
|
|
||||||
|
await context.Database.MigrateAsync();
|
||||||
|
logger.LogInformation("Migrations applied successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task SeedAsync(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
|
|
||||||
|
var seedersManager = scope.ServiceProvider.GetRequiredService<ISeedManager>();
|
||||||
|
|
||||||
|
await seedersManager.ExecuteAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,4 +4,8 @@ namespace BuildingBlocks.EFCore
|
|||||||
{
|
{
|
||||||
Task SeedAllAsync();
|
Task SeedAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ITestDataSeeder : IDataSeeder
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/BuildingBlocks/EFCore/ISeedManager.cs
Normal file
6
src/BuildingBlocks/EFCore/ISeedManager.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
|
public interface ISeedManager
|
||||||
|
{
|
||||||
|
Task ExecuteAsync();
|
||||||
|
}
|
||||||
39
src/BuildingBlocks/EFCore/SeedManagers.cs
Normal file
39
src/BuildingBlocks/EFCore/SeedManagers.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
|
public class SeedManager(
|
||||||
|
IServiceProvider serviceProvider
|
||||||
|
)
|
||||||
|
: ISeedManager
|
||||||
|
{
|
||||||
|
public async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<SeedManager>>();
|
||||||
|
var env = scope.ServiceProvider.GetRequiredService<IWebHostEnvironment>();
|
||||||
|
var dataSeeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
||||||
|
|
||||||
|
if (env.IsEnvironment("test"))
|
||||||
|
{
|
||||||
|
foreach (var seeder in dataSeeders.Where(x => x is ITestDataSeeder))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Test Seed {SeederName} is started.", seeder.GetType().Name);
|
||||||
|
await seeder.SeedAllAsync();
|
||||||
|
logger.LogInformation("Test Seed {SeederName} is completed.", seeder.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name);
|
||||||
|
await seeder.SeedAllAsync();
|
||||||
|
logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using BuildingBlocks.Core.Event;
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Humanizer;
|
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization;
|
||||||
using MongoDB.Bson.Serialization.Conventions;
|
using MongoDB.Bson.Serialization.Conventions;
|
||||||
|
using MongoDB.Bson.Serialization.Serializers;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
|
|
||||||
namespace BuildingBlocks.Mongo;
|
namespace BuildingBlocks.Mongo;
|
||||||
@ -14,6 +16,12 @@ public class MongoDbContext : IMongoDbContext
|
|||||||
public IMongoDatabase Database { get; }
|
public IMongoDatabase Database { get; }
|
||||||
public IMongoClient MongoClient { get; }
|
public IMongoClient MongoClient { get; }
|
||||||
protected readonly IList<Func<Task>> _commands;
|
protected readonly IList<Func<Task>> _commands;
|
||||||
|
private static readonly bool _isSerializerRegisterd;
|
||||||
|
|
||||||
|
static MongoDbContext()
|
||||||
|
{
|
||||||
|
BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String));
|
||||||
|
}
|
||||||
|
|
||||||
public MongoDbContext(IOptions<MongoOptions> options)
|
public MongoDbContext(IOptions<MongoOptions> options)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using BuildingBlocks.Core.Model;
|
using BuildingBlocks.Core.Model;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
namespace BuildingBlocks.Mongo;
|
namespace BuildingBlocks.Mongo;
|
||||||
|
|
||||||
|
|||||||
55
src/BuildingBlocks/OpenApi/Extensions.cs
Normal file
55
src/BuildingBlocks/OpenApi/Extensions.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Scalar.AspNetCore;
|
||||||
|
|
||||||
|
namespace BuildingBlocks.OpenApi
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
// ref: https://github.com/dotnet/eShop/blob/main/src/eShop.ServiceDefaults/OpenApi.Extensions.cs
|
||||||
|
public static IServiceCollection AddAspnetOpenApi(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
string[] versions = ["v1"];
|
||||||
|
|
||||||
|
foreach (var description in versions)
|
||||||
|
{
|
||||||
|
services.AddOpenApi(
|
||||||
|
description,
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
options.AddDocumentTransformer<SecuritySchemeDocumentTransformer>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IApplicationBuilder UseAspnetOpenApi(this WebApplication app)
|
||||||
|
{
|
||||||
|
app.MapOpenApi();
|
||||||
|
|
||||||
|
app.UseSwaggerUI(
|
||||||
|
options =>
|
||||||
|
{
|
||||||
|
var descriptions = app.DescribeApiVersions();
|
||||||
|
|
||||||
|
// build a swagger endpoint for each discovered API version
|
||||||
|
foreach (var description in descriptions)
|
||||||
|
{
|
||||||
|
var openApiUrl = $"/openapi/{description.GroupName}.json";
|
||||||
|
var name = description.GroupName.ToUpperInvariant();
|
||||||
|
options.SwaggerEndpoint(openApiUrl, name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add scalar ui
|
||||||
|
app.MapScalarApiReference(
|
||||||
|
redocOptions =>
|
||||||
|
{
|
||||||
|
redocOptions.WithOpenApiRoutePattern("/openapi/{documentName}.json");
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.AspNetCore.OpenApi;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
|
public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer
|
||||||
|
{
|
||||||
|
public Task TransformAsync(
|
||||||
|
OpenApiDocument document,
|
||||||
|
OpenApiDocumentTransformerContext context,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
|
{
|
||||||
|
document.Components ??= new();
|
||||||
|
|
||||||
|
// Bearer token scheme
|
||||||
|
document.Components.SecuritySchemes.Add(
|
||||||
|
"Bearer",
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Name = "Authorization",
|
||||||
|
Type = SecuritySchemeType.Http,
|
||||||
|
Scheme = "bearer",
|
||||||
|
BearerFormat = "JWT",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Description =
|
||||||
|
"Enter 'Bearer' [space] and your token in the text input below.\n\nExample: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// API Key scheme
|
||||||
|
document.Components.SecuritySchemes.Add(
|
||||||
|
"ApiKey",
|
||||||
|
new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Name = "X-API-KEY",
|
||||||
|
Type = SecuritySchemeType.ApiKey,
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Description =
|
||||||
|
"Enter your API key in the text input below.\n\nExample: '12345-abcdef'",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,33 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.PersistMessageProcessor.Data.Configurations;
|
|
||||||
|
|
||||||
public class PersistMessageConfiguration : IEntityTypeConfiguration<PersistMessage>
|
|
||||||
{
|
|
||||||
public void Configure(EntityTypeBuilder<PersistMessage> builder)
|
|
||||||
{
|
|
||||||
builder.ToTable(nameof(PersistMessage));
|
|
||||||
|
|
||||||
builder.HasKey(x => x.Id);
|
|
||||||
|
|
||||||
builder.Property(r => r.Id)
|
|
||||||
.IsRequired().ValueGeneratedNever();
|
|
||||||
|
|
||||||
// // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api
|
|
||||||
builder.Property(r => r.Version).IsConcurrencyToken();
|
|
||||||
|
|
||||||
builder.Property(x => x.DeliveryType)
|
|
||||||
.HasDefaultValue(MessageDeliveryType.Outbox)
|
|
||||||
.HasConversion(
|
|
||||||
x => x.ToString(),
|
|
||||||
x => (MessageDeliveryType)Enum.Parse(typeof(MessageDeliveryType), x));
|
|
||||||
|
|
||||||
|
|
||||||
builder.Property(x => x.MessageStatus)
|
|
||||||
.HasDefaultValue(MessageStatus.InProgress)
|
|
||||||
.HasConversion(
|
|
||||||
v => v.ToString(),
|
|
||||||
v => (MessageStatus)Enum.Parse(typeof(MessageStatus), v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,51 +1,57 @@
|
|||||||
using BuildingBlocks.PersistMessageProcessor.Data;
|
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace BuildingBlocks.PersistMessageProcessor;
|
namespace BuildingBlocks.PersistMessageProcessor;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddPersistMessageProcessor(this IServiceCollection services,
|
public static IServiceCollection AddPersistMessageProcessor(
|
||||||
IWebHostEnvironment env)
|
this IServiceCollection services,
|
||||||
|
IWebHostEnvironment env
|
||||||
|
)
|
||||||
{
|
{
|
||||||
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
||||||
|
|
||||||
services.AddValidateOptions<PersistMessageOptions>();
|
services.AddValidateOptions<PersistMessageOptions>();
|
||||||
|
|
||||||
services.AddDbContext<PersistMessageDbContext>((sp, options) =>
|
services.AddDbContext<PersistMessageDbContext>(
|
||||||
{
|
(sp, options) =>
|
||||||
var persistMessageOptions = sp.GetRequiredService<PersistMessageOptions>();
|
{
|
||||||
|
var persistMessageOptions = sp.GetRequiredService<PersistMessageOptions>();
|
||||||
|
|
||||||
options.UseNpgsql(persistMessageOptions.ConnectionString,
|
options.UseNpgsql(
|
||||||
dbOptions =>
|
persistMessageOptions.ConnectionString,
|
||||||
{
|
dbOptions =>
|
||||||
dbOptions.MigrationsAssembly(typeof(PersistMessageDbContext).Assembly.GetName().Name);
|
{
|
||||||
})
|
dbOptions.MigrationsAssembly(
|
||||||
// https://github.com/efcore/EFCore.NamingConventions
|
typeof(PersistMessageDbContext).Assembly.GetName().Name);
|
||||||
.UseSnakeCaseNamingConvention();
|
})
|
||||||
});
|
// https://github.com/efcore/EFCore.NamingConventions
|
||||||
|
.UseSnakeCaseNamingConvention();
|
||||||
|
|
||||||
services.AddScoped<IPersistMessageDbContext>(provider =>
|
// Todo: follow up the issues of .net 9 to use better approach taht will provide by .net!
|
||||||
{
|
options.ConfigureWarnings(
|
||||||
var persistMessageDbContext = provider.GetRequiredService<PersistMessageDbContext>();
|
w => w.Ignore(RelationalEventId.PendingModelChangesWarning));
|
||||||
|
});
|
||||||
|
|
||||||
persistMessageDbContext.Database.EnsureCreated();
|
services.AddScoped<IPersistMessageDbContext>(
|
||||||
persistMessageDbContext.CreatePersistMessageTable();
|
provider =>
|
||||||
|
{
|
||||||
|
var persistMessageDbContext =
|
||||||
|
provider.GetRequiredService<PersistMessageDbContext>();
|
||||||
|
|
||||||
return persistMessageDbContext;
|
persistMessageDbContext.Database.EnsureCreated();
|
||||||
});
|
persistMessageDbContext.CreatePersistMessageTableIfNotExists();
|
||||||
|
|
||||||
|
return persistMessageDbContext;
|
||||||
|
});
|
||||||
|
|
||||||
services.AddScoped<IPersistMessageProcessor, PersistMessageProcessor>();
|
services.AddScoped<IPersistMessageProcessor, PersistMessageProcessor>();
|
||||||
|
|
||||||
if (env.EnvironmentName != "test")
|
services.AddHostedService<PersistMessageBackgroundService>();
|
||||||
{
|
|
||||||
services.AddHostedService<PersistMessageBackgroundService>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ namespace BuildingBlocks.PersistMessageProcessor;
|
|||||||
|
|
||||||
public interface IPersistMessageDbContext
|
public interface IPersistMessageDbContext
|
||||||
{
|
{
|
||||||
DbSet<PersistMessage> PersistMessages { get; }
|
DbSet<PersistMessage> PersistMessage { get; }
|
||||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||||
Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default);
|
Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,8 @@ namespace BuildingBlocks.PersistMessageProcessor;
|
|||||||
[Flags]
|
[Flags]
|
||||||
public enum MessageDeliveryType
|
public enum MessageDeliveryType
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
Outbox = 1,
|
Outbox = 1,
|
||||||
Inbox = 2,
|
Inbox = 2,
|
||||||
Internal = 4
|
Internal = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ namespace BuildingBlocks.PersistMessageProcessor;
|
|||||||
|
|
||||||
public enum MessageStatus
|
public enum MessageStatus
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
InProgress = 1,
|
InProgress = 1,
|
||||||
Processed = 2
|
Processed = 2
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
using BuildingBlocks.Core.Model;
|
||||||
namespace BuildingBlocks.PersistMessageProcessor;
|
namespace BuildingBlocks.PersistMessageProcessor;
|
||||||
|
|
||||||
using Core.Model;
|
|
||||||
|
|
||||||
public class PersistMessage : IVersion
|
public class PersistMessage : IVersion
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,27 +5,20 @@ using Microsoft.Extensions.Options;
|
|||||||
|
|
||||||
namespace BuildingBlocks.PersistMessageProcessor;
|
namespace BuildingBlocks.PersistMessageProcessor;
|
||||||
|
|
||||||
public class PersistMessageBackgroundService : BackgroundService
|
public class PersistMessageBackgroundService(
|
||||||
|
ILogger<PersistMessageBackgroundService> logger,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
IOptions<PersistMessageOptions> options
|
||||||
|
)
|
||||||
|
: BackgroundService
|
||||||
{
|
{
|
||||||
private readonly ILogger<PersistMessageBackgroundService> _logger;
|
private PersistMessageOptions _options = options.Value;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private PersistMessageOptions _options;
|
|
||||||
|
|
||||||
private Task? _executingTask;
|
private Task? _executingTask;
|
||||||
|
|
||||||
public PersistMessageBackgroundService(
|
|
||||||
ILogger<PersistMessageBackgroundService> logger,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
IOptions<PersistMessageOptions> options)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_options = options.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("PersistMessage Background Service Start");
|
logger.LogInformation("PersistMessage Background Service Start");
|
||||||
|
|
||||||
_executingTask = ProcessAsync(stoppingToken);
|
_executingTask = ProcessAsync(stoppingToken);
|
||||||
|
|
||||||
@ -34,7 +27,7 @@ public class PersistMessageBackgroundService : BackgroundService
|
|||||||
|
|
||||||
public override Task StopAsync(CancellationToken cancellationToken)
|
public override Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("PersistMessage Background Service Stop");
|
logger.LogInformation("PersistMessage Background Service Stop");
|
||||||
|
|
||||||
return base.StopAsync(cancellationToken);
|
return base.StopAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
@ -43,15 +36,15 @@ public class PersistMessageBackgroundService : BackgroundService
|
|||||||
{
|
{
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await using (var scope = _serviceProvider.CreateAsyncScope())
|
await using (var scope = serviceProvider.CreateAsyncScope())
|
||||||
{
|
{
|
||||||
var service = scope.ServiceProvider.GetRequiredService<IPersistMessageProcessor>();
|
var service = scope.ServiceProvider.GetRequiredService<IPersistMessageProcessor>();
|
||||||
await service.ProcessAllAsync(stoppingToken);
|
await service.ProcessAllAsync(stoppingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
var delay = _options.Interval is { }
|
var delay = _options.Interval is { }
|
||||||
? TimeSpan.FromSeconds((int)_options.Interval)
|
? TimeSpan.FromSeconds((int)_options.Interval)
|
||||||
: TimeSpan.FromSeconds(30);
|
: TimeSpan.FromSeconds(30);
|
||||||
|
|
||||||
await Task.Delay(delay, stoppingToken);
|
await Task.Delay(delay, stoppingToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
|
using BuildingBlocks.Core.Model;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace BuildingBlocks.PersistMessageProcessor.Data;
|
|
||||||
|
|
||||||
using Configurations;
|
|
||||||
using Core.Model;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Exception = System.Exception;
|
|
||||||
using IsolationLevel = System.Data.IsolationLevel;
|
using IsolationLevel = System.Data.IsolationLevel;
|
||||||
|
|
||||||
|
namespace BuildingBlocks.PersistMessageProcessor;
|
||||||
|
|
||||||
public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
||||||
{
|
{
|
||||||
private readonly ILogger<PersistMessageDbContext>? _logger;
|
private readonly ILogger<PersistMessageDbContext>? _logger;
|
||||||
@ -20,11 +17,10 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<PersistMessage> PersistMessages => Set<PersistMessage>();
|
public DbSet<PersistMessage> PersistMessage => Set<PersistMessage>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
builder.ApplyConfiguration(new PersistMessageConfiguration());
|
|
||||||
base.OnModelCreating(builder);
|
base.OnModelCreating(builder);
|
||||||
builder.ToSnakeCaseTables();
|
builder.ToSnakeCaseTables();
|
||||||
}
|
}
|
||||||
@ -79,13 +75,8 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreatePersistMessageTable()
|
public void CreatePersistMessageTableIfNotExists()
|
||||||
{
|
{
|
||||||
if (Database.GetPendingMigrations().Any())
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot create table if there are pending migrations.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string createTableSql = @"
|
string createTableSql = @"
|
||||||
create table if not exists persist_message (
|
create table if not exists persist_message (
|
||||||
id uuid not null,
|
id uuid not null,
|
||||||
@ -120,9 +111,9 @@ public class PersistMessageDbContext : DbContext, IPersistMessageDbContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (System.Exception ex)
|
||||||
{
|
{
|
||||||
throw new Exception("try for find IVersion", ex);
|
throw new System.Exception("try for find IVersion", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,13 +54,13 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
public async Task<IReadOnlyList<PersistMessage>> GetByFilterAsync(Expression<Func<PersistMessage, bool>> predicate,
|
public async Task<IReadOnlyList<PersistMessage>> GetByFilterAsync(Expression<Func<PersistMessage, bool>> predicate,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return (await _persistMessageDbContext.PersistMessages.Where(predicate).ToListAsync(cancellationToken))
|
return (await _persistMessageDbContext.PersistMessage.Where(predicate).ToListAsync(cancellationToken))
|
||||||
.AsReadOnly();
|
.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PersistMessage> ExistMessageAsync(Guid messageId, CancellationToken cancellationToken = default)
|
public Task<PersistMessage> ExistMessageAsync(Guid messageId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync(x =>
|
return _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync(x =>
|
||||||
x.Id == messageId &&
|
x.Id == messageId &&
|
||||||
x.DeliveryType == MessageDeliveryType.Inbox &&
|
x.DeliveryType == MessageDeliveryType.Inbox &&
|
||||||
x.MessageStatus == MessageStatus.Processed,
|
x.MessageStatus == MessageStatus.Processed,
|
||||||
@ -73,7 +73,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var message =
|
var message =
|
||||||
await _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync(
|
await _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync(
|
||||||
x => x.Id == messageId && x.DeliveryType == deliveryType, cancellationToken);
|
x => x.Id == messageId && x.DeliveryType == deliveryType, cancellationToken);
|
||||||
|
|
||||||
if (message is null)
|
if (message is null)
|
||||||
@ -109,7 +109,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
|
|
||||||
public async Task ProcessAllAsync(CancellationToken cancellationToken = default)
|
public async Task ProcessAllAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var messages = await _persistMessageDbContext.PersistMessages
|
var messages = await _persistMessageDbContext.PersistMessage
|
||||||
.Where(x => x.MessageStatus != MessageStatus.Processed)
|
.Where(x => x.MessageStatus != MessageStatus.Processed)
|
||||||
.ToListAsync(cancellationToken);
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
|
|
||||||
public async Task ProcessInboxAsync(Guid messageId, CancellationToken cancellationToken = default)
|
public async Task ProcessInboxAsync(Guid messageId, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var message = await _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync(
|
var message = await _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync(
|
||||||
x => x.Id == messageId &&
|
x => x.Id == messageId &&
|
||||||
x.DeliveryType == MessageDeliveryType.Inbox &&
|
x.DeliveryType == MessageDeliveryType.Inbox &&
|
||||||
x.MessageStatus == MessageStatus.InProgress,
|
x.MessageStatus == MessageStatus.InProgress,
|
||||||
@ -193,7 +193,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
else
|
else
|
||||||
id = NewId.NextGuid();
|
id = NewId.NextGuid();
|
||||||
|
|
||||||
await _persistMessageDbContext.PersistMessages.AddAsync(
|
await _persistMessageDbContext.PersistMessage.AddAsync(
|
||||||
new PersistMessage(
|
new PersistMessage(
|
||||||
id,
|
id,
|
||||||
messageEnvelope.Message.GetType().ToString(),
|
messageEnvelope.Message.GetType().ToString(),
|
||||||
@ -215,7 +215,7 @@ public class PersistMessageProcessor : IPersistMessageProcessor
|
|||||||
{
|
{
|
||||||
message.ChangeState(MessageStatus.Processed);
|
message.ChangeState(MessageStatus.Processed);
|
||||||
|
|
||||||
_persistMessageDbContext.PersistMessages.Update(message);
|
_persistMessageDbContext.PersistMessage.Update(message);
|
||||||
|
|
||||||
await _persistMessageDbContext.SaveChangesAsync(cancellationToken);
|
await _persistMessageDbContext.SaveChangesAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Asp.Versioning;
|
|
||||||
using Asp.Versioning.ApiExplorer;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Swagger;
|
|
||||||
|
|
||||||
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
|
|
||||||
{
|
|
||||||
private readonly IApiVersionDescriptionProvider provider;
|
|
||||||
private readonly SwaggerOptions? _options;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="ConfigureSwaggerOptions"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="provider">The <see cref="IApiVersionDescriptionProvider">provider</see> used to generate Swagger documents.</param>
|
|
||||||
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IOptions<SwaggerOptions> options)
|
|
||||||
{
|
|
||||||
this.provider = provider;
|
|
||||||
_options = options.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Configure(SwaggerGenOptions options)
|
|
||||||
{
|
|
||||||
// add a swagger document for each discovered API version
|
|
||||||
// note: you might choose to skip or document deprecated API versions differently
|
|
||||||
foreach (var description in provider.ApiVersionDescriptions)
|
|
||||||
{
|
|
||||||
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
|
|
||||||
{
|
|
||||||
var text = new StringBuilder("An example application with OpenAPI, Swashbuckle, and API versioning.");
|
|
||||||
var info = new OpenApiInfo
|
|
||||||
{
|
|
||||||
Version = description.ApiVersion.ToString(),
|
|
||||||
Title = _options?.Title ?? "APIs",
|
|
||||||
Description = "An application with Swagger, Swashbuckle, and API versioning.",
|
|
||||||
Contact = new OpenApiContact { Name = "", Email = "" },
|
|
||||||
License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (description.IsDeprecated)
|
|
||||||
{
|
|
||||||
text.Append("This API version has been deprecated.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description.SunsetPolicy is SunsetPolicy policy)
|
|
||||||
{
|
|
||||||
if (policy.Date is DateTimeOffset when)
|
|
||||||
{
|
|
||||||
text.Append(" The API will be sunset on ")
|
|
||||||
.Append(when.Date.ToShortDateString())
|
|
||||||
.Append('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (policy.HasLinks)
|
|
||||||
{
|
|
||||||
text.AppendLine();
|
|
||||||
|
|
||||||
for (var i = 0; i < policy.Links.Count; i++)
|
|
||||||
{
|
|
||||||
var link = policy.Links[i];
|
|
||||||
|
|
||||||
if (link.Type == "text/html")
|
|
||||||
{
|
|
||||||
text.AppendLine();
|
|
||||||
|
|
||||||
if (link.Title.HasValue)
|
|
||||||
{
|
|
||||||
text.Append(link.Title.Value).Append(": ");
|
|
||||||
}
|
|
||||||
|
|
||||||
text.Append(link.LinkTarget.OriginalString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.Description = text.ToString();
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,107 +0,0 @@
|
|||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Swagger;
|
|
||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
|
||||||
{
|
|
||||||
public const string HeaderName = "X-Api-Key";
|
|
||||||
public const string HeaderVersion = "api-version";
|
|
||||||
|
|
||||||
// https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md
|
|
||||||
// https://github.com/dotnet/aspnet-api-versioning/tree/88323136a97a59fcee24517a514c1a445530c7e2/examples/AspNetCore/WebApi/MinimalOpenApiExample
|
|
||||||
public static IServiceCollection AddCustomSwagger(this IServiceCollection services,
|
|
||||||
IConfiguration configuration,
|
|
||||||
params Assembly[] assemblies)
|
|
||||||
{
|
|
||||||
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
|
|
||||||
services.AddOptions<SwaggerOptions>().Bind(configuration.GetSection(nameof(SwaggerOptions)))
|
|
||||||
.ValidateDataAnnotations();
|
|
||||||
|
|
||||||
services.AddSwaggerGen(
|
|
||||||
options =>
|
|
||||||
{
|
|
||||||
options.OperationFilter<SwaggerDefaultValues>();
|
|
||||||
|
|
||||||
foreach (var assembly in assemblies)
|
|
||||||
{
|
|
||||||
var xmlFile = XmlCommentsFilePath(assembly);
|
|
||||||
|
|
||||||
if (File.Exists(xmlFile))
|
|
||||||
options.IncludeXmlComments(xmlFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
options.AddEnumsWithValuesFixFilters();
|
|
||||||
|
|
||||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
BearerFormat = "JWT",
|
|
||||||
Scheme = "oauth2",
|
|
||||||
Name = "Bearer",
|
|
||||||
In = ParameterLocation.Header
|
|
||||||
});
|
|
||||||
|
|
||||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
|
||||||
{
|
|
||||||
{
|
|
||||||
new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Reference = new OpenApiReference
|
|
||||||
{
|
|
||||||
Type=ReferenceType.SecurityScheme,
|
|
||||||
Id="Bearer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new string[]{}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
|
||||||
|
|
||||||
////https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/467
|
|
||||||
// options.OperationFilter<TagByApiExplorerSettingsOperationFilter>();
|
|
||||||
// options.OperationFilter<TagBySwaggerOperationFilter>();
|
|
||||||
|
|
||||||
// Enables Swagger annotations (SwaggerOperationAttribute, SwaggerParameterAttribute etc.)
|
|
||||||
// options.EnableAnnotations();
|
|
||||||
});
|
|
||||||
|
|
||||||
// services.Configure<SwaggerGeneratorOptions>(o => o.InferSecuritySchemes = true);
|
|
||||||
|
|
||||||
static string XmlCommentsFilePath(Assembly assembly)
|
|
||||||
{
|
|
||||||
var basePath = Path.GetDirectoryName(assembly.Location);
|
|
||||||
var fileName = assembly.GetName().Name + ".xml";
|
|
||||||
return Path.Combine(basePath, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IApplicationBuilder UseCustomSwagger(this WebApplication app)
|
|
||||||
{
|
|
||||||
app.UseSwagger();
|
|
||||||
app.UseSwaggerUI(
|
|
||||||
options =>
|
|
||||||
{
|
|
||||||
var descriptions = app.DescribeApiVersions();
|
|
||||||
|
|
||||||
// build a swagger endpoint for each discovered API version
|
|
||||||
foreach (var description in descriptions)
|
|
||||||
{
|
|
||||||
var url = $"/swagger/{description.GroupName}/swagger.json";
|
|
||||||
var name = description.GroupName.ToUpperInvariant();
|
|
||||||
options.SwaggerEndpoint(url, name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using Humanizer;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Swagger
|
|
||||||
{
|
|
||||||
public class SwaggerDefaultValues : IOperationFilter
|
|
||||||
{
|
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
|
||||||
{
|
|
||||||
|
|
||||||
var apiDescription = context.ApiDescription;
|
|
||||||
|
|
||||||
operation.Deprecated |= apiDescription.IsDeprecated();
|
|
||||||
|
|
||||||
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077
|
|
||||||
foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
|
|
||||||
{
|
|
||||||
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387
|
|
||||||
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
|
|
||||||
var response = operation.Responses[responseKey];
|
|
||||||
|
|
||||||
foreach (var contentType in response.Content.Keys)
|
|
||||||
{
|
|
||||||
if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType))
|
|
||||||
{
|
|
||||||
response.Content.Remove(contentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.Parameters == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412
|
|
||||||
// REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413
|
|
||||||
foreach (var parameter in operation.Parameters)
|
|
||||||
{
|
|
||||||
var description = apiDescription.ParameterDescriptions.FirstOrDefault(p => p.Name == parameter.Name);
|
|
||||||
|
|
||||||
if (description is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter.Description == null)
|
|
||||||
{
|
|
||||||
parameter.Description = description.ModelMetadata?.Description;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter.Name = description.Name.Camelize();
|
|
||||||
|
|
||||||
if (parameter.Schema.Default == null && description.DefaultValue != null)
|
|
||||||
{
|
|
||||||
// REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330
|
|
||||||
var json = JsonConvert.SerializeObject(description.DefaultValue, description.ModelMetadata
|
|
||||||
.ModelType, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
|
|
||||||
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter.Required |= description.IsRequired;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
namespace BuildingBlocks.Swagger
|
|
||||||
{
|
|
||||||
public class SwaggerOptions
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Version { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Ardalis.GuardClauses;
|
using Ardalis.GuardClauses;
|
||||||
@ -9,7 +10,6 @@ using BuildingBlocks.PersistMessageProcessor;
|
|||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using EasyNetQ.Management.Client;
|
using EasyNetQ.Management.Client;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using MassTransit;
|
|
||||||
using MassTransit.Testing;
|
using MassTransit.Testing;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@ -18,10 +18,17 @@ using Microsoft.AspNetCore.Mvc.Testing;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using MongoDB.Driver;
|
using MongoDB.Driver;
|
||||||
|
using Npgsql;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Respawn;
|
using Respawn;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Testcontainers.EventStoreDb;
|
||||||
|
using Testcontainers.MongoDb;
|
||||||
|
using Testcontainers.PostgreSql;
|
||||||
|
using Testcontainers.RabbitMq;
|
||||||
using WebMotions.Fake.Authentication.JwtBearer;
|
using WebMotions.Fake.Authentication.JwtBearer;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
@ -29,12 +36,6 @@ using ILogger = Serilog.ILogger;
|
|||||||
|
|
||||||
namespace BuildingBlocks.TestBase;
|
namespace BuildingBlocks.TestBase;
|
||||||
|
|
||||||
using System.Globalization;
|
|
||||||
using Npgsql;
|
|
||||||
using Testcontainers.EventStoreDb;
|
|
||||||
using Testcontainers.MongoDb;
|
|
||||||
using Testcontainers.PostgreSql;
|
|
||||||
using Testcontainers.RabbitMq;
|
|
||||||
|
|
||||||
public class TestFixture<TEntryPoint> : IAsyncLifetime
|
public class TestFixture<TEntryPoint> : IAsyncLifetime
|
||||||
where TEntryPoint : class
|
where TEntryPoint : class
|
||||||
@ -50,8 +51,7 @@ where TEntryPoint : class
|
|||||||
public EventStoreDbContainer EventStoreDbTestContainer;
|
public EventStoreDbContainer EventStoreDbTestContainer;
|
||||||
public CancellationTokenSource CancellationTokenSource;
|
public CancellationTokenSource CancellationTokenSource;
|
||||||
|
|
||||||
public PersistMessageBackgroundService PersistMessageBackgroundService =>
|
public PersistMessageBackgroundService PersistMessageBackgroundService => ServiceProvider.GetRequiredService<PersistMessageBackgroundService>();
|
||||||
ServiceProvider.GetRequiredService<PersistMessageBackgroundService>();
|
|
||||||
|
|
||||||
public HttpClient HttpClient
|
public HttpClient HttpClient
|
||||||
{
|
{
|
||||||
@ -95,9 +95,17 @@ where TEntryPoint : class
|
|||||||
{
|
{
|
||||||
TestRegistrationServices?.Invoke(services);
|
TestRegistrationServices?.Invoke(services);
|
||||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||||
|
services.RemoveAll<IHostedService>();
|
||||||
|
|
||||||
services.AddSingleton<PersistMessageBackgroundService>();
|
services.AddSingleton<PersistMessageBackgroundService>();
|
||||||
|
|
||||||
|
// Register all ITestDataSeeder implementations dynamically
|
||||||
|
services.Scan(scan => scan
|
||||||
|
.FromApplicationDependencies() // Scan the current app and its dependencies
|
||||||
|
.AddClasses(classes => classes.AssignableTo<ITestDataSeeder>()) // Find classes that implement ITestDataSeeder
|
||||||
|
.AsImplementedInterfaces()
|
||||||
|
.WithScopedLifetime());
|
||||||
|
|
||||||
// add authentication using a fake jwt bearer - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor
|
// add authentication using a fake jwt bearer - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor
|
||||||
// https://github.com/webmotions/fake-authentication-jwtbearer
|
// https://github.com/webmotions/fake-authentication-jwtbearer
|
||||||
// https://github.com/webmotions/fake-authentication-jwtbearer/issues/14
|
// https://github.com/webmotions/fake-authentication-jwtbearer/issues/14
|
||||||
@ -200,14 +208,8 @@ where TEntryPoint : class
|
|||||||
var result = await WaitUntilConditionMet(
|
var result = await WaitUntilConditionMet(
|
||||||
async () =>
|
async () =>
|
||||||
{
|
{
|
||||||
var published =
|
var published = await TestHarness.Published.Any<TMessage>(cancellationToken);
|
||||||
await TestHarness.Published.Any<TMessage>(cancellationToken);
|
return published;
|
||||||
|
|
||||||
var faulty =
|
|
||||||
await TestHarness.Published.Any<Fault<TMessage>>(
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
return published && faulty == false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -224,10 +226,7 @@ where TEntryPoint : class
|
|||||||
var consumed =
|
var consumed =
|
||||||
await TestHarness.Consumed.Any<TMessage>(cancellationToken);
|
await TestHarness.Consumed.Any<TMessage>(cancellationToken);
|
||||||
|
|
||||||
var faulty =
|
return consumed;
|
||||||
await TestHarness.Consumed.Any<Fault<TMessage>>(cancellationToken);
|
|
||||||
|
|
||||||
return consumed && faulty == false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -611,8 +610,6 @@ where TEntryPoint : class
|
|||||||
_reSpawnerDefaultDb = await Respawner.CreateAsync(
|
_reSpawnerDefaultDb = await Respawner.CreateAsync(
|
||||||
DefaultDbConnection,
|
DefaultDbConnection,
|
||||||
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
new RespawnerOptions { DbAdapter = DbAdapter.Postgres });
|
||||||
|
|
||||||
await SeedDataAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,18 +677,6 @@ where TEntryPoint : class
|
|||||||
protected virtual void RegisterTestsServices(IServiceCollection services)
|
protected virtual void RegisterTestsServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SeedDataAsync()
|
|
||||||
{
|
|
||||||
using var scope = Fixture.ServiceProvider.CreateScope();
|
|
||||||
|
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
|
||||||
|
|
||||||
foreach (var seeder in seeders)
|
|
||||||
{
|
|
||||||
await seeder.SeedAllAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class TestReadBase<TEntryPoint, TRContext> : TestFixtureCore<TEntryPoint>
|
public abstract class TestReadBase<TEntryPoint, TRContext> : TestFixtureCore<TEntryPoint>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.66.0">
|
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -8,9 +8,10 @@ using BuildingBlocks.Logging;
|
|||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
using BuildingBlocks.Mongo;
|
using BuildingBlocks.Mongo;
|
||||||
|
using BuildingBlocks.OpenApi;
|
||||||
using BuildingBlocks.OpenTelemetry;
|
using BuildingBlocks.OpenTelemetry;
|
||||||
using BuildingBlocks.PersistMessageProcessor;
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@ -19,13 +20,10 @@ using Microsoft.AspNetCore.Http;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Prometheus;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Booking.Extensions.Infrastructure;
|
namespace Booking.Extensions.Infrastructure;
|
||||||
|
|
||||||
using BuildingBlocks.ProblemDetails;
|
|
||||||
|
|
||||||
public static class InfrastructureExtensions
|
public static class InfrastructureExtensions
|
||||||
{
|
{
|
||||||
public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder)
|
public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder)
|
||||||
@ -67,7 +65,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.AddCustomSerilog(env);
|
builder.AddCustomSerilog(env);
|
||||||
builder.Services.AddJwt();
|
builder.Services.AddJwt();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddCustomSwagger(configuration, typeof(BookingRoot).Assembly);
|
builder.Services.AddAspnetOpenApi();
|
||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly);
|
||||||
@ -106,7 +104,7 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseCustomSwagger();
|
app.UseAspnetOpenApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace Flight.Data.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "7.0.2")
|
.HasAnnotation("ProductVersion", "9.0.0")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
using Flight.Aircrafts.Models;
|
using Flight.Aircrafts.Models;
|
||||||
using Flight.Airports.Models;
|
using Flight.Airports.Models;
|
||||||
|
using Flight.Flights.Models;
|
||||||
using Flight.Seats.Models;
|
using Flight.Seats.Models;
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -11,23 +10,12 @@ using MongoDB.Driver.Linq;
|
|||||||
|
|
||||||
namespace Flight.Data.Seed;
|
namespace Flight.Data.Seed;
|
||||||
|
|
||||||
using Flights.Models;
|
public class FlightDataSeeder(
|
||||||
|
FlightDbContext flightDbContext,
|
||||||
public class FlightDataSeeder : IDataSeeder
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper
|
||||||
|
) : IDataSeeder
|
||||||
{
|
{
|
||||||
private readonly FlightDbContext _flightDbContext;
|
|
||||||
private readonly FlightReadDbContext _flightReadDbContext;
|
|
||||||
private readonly IMapper _mapper;
|
|
||||||
|
|
||||||
public FlightDataSeeder(FlightDbContext flightDbContext,
|
|
||||||
FlightReadDbContext flightReadDbContext,
|
|
||||||
IMapper mapper)
|
|
||||||
{
|
|
||||||
_flightDbContext = flightDbContext;
|
|
||||||
_flightReadDbContext = flightReadDbContext;
|
|
||||||
_mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SeedAllAsync()
|
public async Task SeedAllAsync()
|
||||||
{
|
{
|
||||||
await SeedAirportAsync();
|
await SeedAirportAsync();
|
||||||
@ -38,28 +26,28 @@ public class FlightDataSeeder : IDataSeeder
|
|||||||
|
|
||||||
private async Task SeedAirportAsync()
|
private async Task SeedAirportAsync()
|
||||||
{
|
{
|
||||||
if (!await _flightDbContext.Airports.AnyAsync())
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports))
|
||||||
{
|
{
|
||||||
await _flightDbContext.Airports.AddRangeAsync(InitialData.Airports);
|
await flightDbContext.Airports.AddRangeAsync(InitialData.Airports);
|
||||||
await _flightDbContext.SaveChangesAsync();
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
if (!await _flightReadDbContext.Airport.AsQueryable().AnyAsync())
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable()))
|
||||||
{
|
{
|
||||||
await _flightReadDbContext.Airport.InsertManyAsync(_mapper.Map<List<AirportReadModel>>(InitialData.Airports));
|
await flightReadDbContext.Airport.InsertManyAsync(mapper.Map<List<AirportReadModel>>(InitialData.Airports));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SeedAircraftAsync()
|
private async Task SeedAircraftAsync()
|
||||||
{
|
{
|
||||||
if (!await _flightDbContext.Aircraft.AnyAsync())
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft))
|
||||||
{
|
{
|
||||||
await _flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts);
|
await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts);
|
||||||
await _flightDbContext.SaveChangesAsync();
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
if (!await _flightReadDbContext.Aircraft.AsQueryable().AnyAsync())
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable()))
|
||||||
{
|
{
|
||||||
await _flightReadDbContext.Aircraft.InsertManyAsync(_mapper.Map<List<AircraftReadModel>>(InitialData.Aircrafts));
|
await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map<List<AircraftReadModel>>(InitialData.Aircrafts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,28 +55,28 @@ public class FlightDataSeeder : IDataSeeder
|
|||||||
|
|
||||||
private async Task SeedSeatAsync()
|
private async Task SeedSeatAsync()
|
||||||
{
|
{
|
||||||
if (!await _flightDbContext.Seats.AnyAsync())
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats))
|
||||||
{
|
{
|
||||||
await _flightDbContext.Seats.AddRangeAsync(InitialData.Seats);
|
await flightDbContext.Seats.AddRangeAsync(InitialData.Seats);
|
||||||
await _flightDbContext.SaveChangesAsync();
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
if (!await _flightReadDbContext.Seat.AsQueryable().AnyAsync())
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable()))
|
||||||
{
|
{
|
||||||
await _flightReadDbContext.Seat.InsertManyAsync(_mapper.Map<List<SeatReadModel>>(InitialData.Seats));
|
await flightReadDbContext.Seat.InsertManyAsync(mapper.Map<List<SeatReadModel>>(InitialData.Seats));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SeedFlightAsync()
|
private async Task SeedFlightAsync()
|
||||||
{
|
{
|
||||||
if (!await _flightDbContext.Flights.AnyAsync())
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights))
|
||||||
{
|
{
|
||||||
await _flightDbContext.Flights.AddRangeAsync(InitialData.Flights);
|
await flightDbContext.Flights.AddRangeAsync(InitialData.Flights);
|
||||||
await _flightDbContext.SaveChangesAsync();
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
if (!await _flightReadDbContext.Flight.AsQueryable().AnyAsync())
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable()))
|
||||||
{
|
{
|
||||||
await _flightReadDbContext.Flight.InsertManyAsync(_mapper.Map<List<FlightReadModel>>(InitialData.Flights));
|
await flightReadDbContext.Flight.InsertManyAsync(mapper.Map<List<FlightReadModel>>(InitialData.Flights));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
@ -9,9 +8,10 @@ using BuildingBlocks.Logging;
|
|||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
using BuildingBlocks.Mongo;
|
using BuildingBlocks.Mongo;
|
||||||
|
using BuildingBlocks.OpenApi;
|
||||||
using BuildingBlocks.OpenTelemetry;
|
using BuildingBlocks.OpenTelemetry;
|
||||||
using BuildingBlocks.PersistMessageProcessor;
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using Flight.Data;
|
using Flight.Data;
|
||||||
@ -27,7 +27,6 @@ using Serilog;
|
|||||||
|
|
||||||
namespace Flight.Extensions.Infrastructure;
|
namespace Flight.Extensions.Infrastructure;
|
||||||
|
|
||||||
using BuildingBlocks.ProblemDetails;
|
|
||||||
|
|
||||||
public static class InfrastructureExtensions
|
public static class InfrastructureExtensions
|
||||||
{
|
{
|
||||||
@ -38,6 +37,7 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||||
builder.Services.AddScoped<IEventMapper, EventMapper>();
|
builder.Services.AddScoped<IEventMapper, EventMapper>();
|
||||||
|
|
||||||
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
||||||
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
||||||
{
|
{
|
||||||
@ -72,7 +72,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.AddCustomSerilog(env);
|
builder.AddCustomSerilog(env);
|
||||||
builder.Services.AddJwt();
|
builder.Services.AddJwt();
|
||||||
builder.Services.AddCustomSwagger(configuration, typeof(FlightRoot).Assembly);
|
builder.Services.AddAspnetOpenApi();
|
||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(FlightRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(FlightRoot).Assembly);
|
||||||
builder.Services.AddCustomMapster(typeof(FlightRoot).Assembly);
|
builder.Services.AddCustomMapster(typeof(FlightRoot).Assembly);
|
||||||
@ -105,7 +105,7 @@ public static class InfrastructureExtensions
|
|||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
});
|
});
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
app.UseMigration<FlightDbContext>(env);
|
app.UseMigration<FlightDbContext>();
|
||||||
app.UseCustomHealthCheck();
|
app.UseCustomHealthCheck();
|
||||||
app.MapGrpcService<FlightGrpcServices>();
|
app.MapGrpcService<FlightGrpcServices>();
|
||||||
app.UseRateLimiter();
|
app.UseRateLimiter();
|
||||||
@ -113,7 +113,7 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseCustomSwagger();
|
app.UseAspnetOpenApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.65.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.65.0" />
|
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.66.0">
|
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,38 +1,44 @@
|
|||||||
namespace Flight.Flights.Features.DeletingFlight.V1;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Ardalis.GuardClauses;
|
using Ardalis.GuardClauses;
|
||||||
using BuildingBlocks.Core.CQRS;
|
using BuildingBlocks.Core.CQRS;
|
||||||
using BuildingBlocks.Core.Event;
|
using BuildingBlocks.Core.Event;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Data;
|
|
||||||
using Duende.IdentityServer.EntityFramework.Entities;
|
using Duende.IdentityServer.EntityFramework.Entities;
|
||||||
using Exceptions;
|
using Flight.Data;
|
||||||
|
using Flight.Flights.Exceptions;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using MongoDB.Driver.Linq;
|
|
||||||
using ValueObjects;
|
namespace Flight.Flights.Features.DeletingFlight.V1;
|
||||||
|
|
||||||
public record DeleteFlight(Guid Id) : ICommand<DeleteFlightResult>, IInternalCommand;
|
public record DeleteFlight(Guid Id) : ICommand<DeleteFlightResult>, IInternalCommand;
|
||||||
|
|
||||||
public record DeleteFlightResult(Guid Id);
|
public record DeleteFlightResult(Guid Id);
|
||||||
|
|
||||||
public record FlightDeletedDomainEvent(Guid Id, string FlightNumber, Guid AircraftId, DateTime DepartureDate,
|
public record FlightDeletedDomainEvent(
|
||||||
Guid DepartureAirportId, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes,
|
Guid Id,
|
||||||
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
string FlightNumber,
|
||||||
|
Guid AircraftId,
|
||||||
|
DateTime DepartureDate,
|
||||||
|
Guid DepartureAirportId,
|
||||||
|
DateTime ArriveDate,
|
||||||
|
Guid ArriveAirportId,
|
||||||
|
decimal DurationMinutes,
|
||||||
|
DateTime FlightDate,
|
||||||
|
Enums.FlightStatus Status,
|
||||||
|
decimal Price,
|
||||||
|
bool IsDeleted
|
||||||
|
) : IDomainEvent;
|
||||||
|
|
||||||
public class DeleteFlightEndpoint : IMinimalEndpoint
|
public class DeleteFlightEndpoint : IMinimalEndpoint
|
||||||
{
|
{
|
||||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}",
|
builder.MapDelete(
|
||||||
|
$"{EndpointConfig.BaseApiPath}/flight/{{id}}",
|
||||||
async (Guid id, IMediator mediator, CancellationToken cancellationToken) =>
|
async (Guid id, IMediator mediator, CancellationToken cancellationToken) =>
|
||||||
{
|
{
|
||||||
await mediator.Send(new DeleteFlight(id), cancellationToken);
|
await mediator.Send(new DeleteFlight(id), cancellationToken);
|
||||||
@ -70,7 +76,10 @@ internal class DeleteFlightHandler : ICommandHandler<DeleteFlight, DeleteFlightR
|
|||||||
_flightDbContext = flightDbContext;
|
_flightDbContext = flightDbContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DeleteFlightResult> Handle(DeleteFlight request, CancellationToken cancellationToken)
|
public async Task<DeleteFlightResult> Handle(
|
||||||
|
DeleteFlight request,
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
Guard.Against.Null(request, nameof(request));
|
Guard.Against.Null(request, nameof(request));
|
||||||
|
|
||||||
@ -81,9 +90,18 @@ internal class DeleteFlightHandler : ICommandHandler<DeleteFlight, DeleteFlightR
|
|||||||
throw new FlightNotFountException();
|
throw new FlightNotFountException();
|
||||||
}
|
}
|
||||||
|
|
||||||
flight.Delete(flight.Id, flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
flight.Delete(
|
||||||
flight.DepartureDate, flight.ArriveDate, flight.ArriveAirportId, flight.DurationMinutes,
|
flight.Id,
|
||||||
flight.FlightDate, flight.Status, flight.Price);
|
flight.FlightNumber,
|
||||||
|
flight.AircraftId,
|
||||||
|
flight.DepartureAirportId,
|
||||||
|
flight.DepartureDate,
|
||||||
|
flight.ArriveDate,
|
||||||
|
flight.ArriveAirportId,
|
||||||
|
flight.DurationMinutes,
|
||||||
|
flight.FlightDate,
|
||||||
|
flight.Status,
|
||||||
|
flight.Price);
|
||||||
|
|
||||||
var deleteFlight = _flightDbContext.Flights.Update(flight).Entity;
|
var deleteFlight = _flightDbContext.Flights.Update(flight).Entity;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
namespace Flight.Flights.Features.GettingAvailableFlights.V1;
|
namespace Flight.Flights.Features.GettingAvailableFlights.V1;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@ -76,9 +76,9 @@ internal class GetFlightByIdHandler : IQueryHandler<GetFlightById, GetFlightById
|
|||||||
{
|
{
|
||||||
Guard.Against.Null(request, nameof(request));
|
Guard.Against.Null(request, nameof(request));
|
||||||
|
|
||||||
var flight =
|
var flight = await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(
|
||||||
await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == request.Id &&
|
x => x.FlightId == request.Id &&
|
||||||
!x.IsDeleted, cancellationToken);
|
!x.IsDeleted, cancellationToken);
|
||||||
|
|
||||||
if (flight is null)
|
if (flight is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
namespace Flight.Seats.Features.GettingAvailableSeats.V1;
|
namespace Flight.Seats.Features.GettingAvailableSeats.V1;
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
using BuildingBlocks.EFCore;
|
||||||
|
using Flight.Aircrafts.Models;
|
||||||
|
using Flight.Airports.Models;
|
||||||
|
using Flight.Data;
|
||||||
|
using Flight.Data.Seed;
|
||||||
|
using Flight.Flights.Models;
|
||||||
|
using Flight.Seats.Models;
|
||||||
|
using MapsterMapper;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace EndToEnd.Test;
|
||||||
|
|
||||||
|
public class FlightTestDataSeeder(
|
||||||
|
FlightDbContext flightDbContext,
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper
|
||||||
|
) : ITestDataSeeder
|
||||||
|
{
|
||||||
|
public async Task SeedAllAsync()
|
||||||
|
{
|
||||||
|
await SeedAirportAsync();
|
||||||
|
await SeedAircraftAsync();
|
||||||
|
await SeedFlightAsync();
|
||||||
|
await SeedSeatAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedAirportAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports))
|
||||||
|
{
|
||||||
|
await flightDbContext.Airports.AddRangeAsync(InitialData.Airports);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Airport.InsertManyAsync(mapper.Map<List<AirportReadModel>>(InitialData.Airports));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedAircraftAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft))
|
||||||
|
{
|
||||||
|
await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map<List<AircraftReadModel>>(InitialData.Aircrafts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task SeedSeatAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats))
|
||||||
|
{
|
||||||
|
await flightDbContext.Seats.AddRangeAsync(InitialData.Seats);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Seat.InsertManyAsync(mapper.Map<List<SeatReadModel>>(InitialData.Seats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedFlightAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights))
|
||||||
|
{
|
||||||
|
await flightDbContext.Flights.AddRangeAsync(InitialData.Flights);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Flight.InsertManyAsync(mapper.Map<List<FlightReadModel>>(InitialData.Flights));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
using BuildingBlocks.TestBase;
|
using BuildingBlocks.TestBase;
|
||||||
using Flight.Api;
|
using Flight.Api;
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
using BuildingBlocks.EFCore;
|
||||||
|
using Flight.Aircrafts.Models;
|
||||||
|
using Flight.Airports.Models;
|
||||||
|
using Flight.Data;
|
||||||
|
using Flight.Data.Seed;
|
||||||
|
using Flight.Flights.Models;
|
||||||
|
using Flight.Seats.Models;
|
||||||
|
using MapsterMapper;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace Integration.Test;
|
||||||
|
|
||||||
|
public class FlightTestDataSeeder(
|
||||||
|
FlightDbContext flightDbContext,
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper
|
||||||
|
) : ITestDataSeeder
|
||||||
|
{
|
||||||
|
public async Task SeedAllAsync()
|
||||||
|
{
|
||||||
|
await SeedAirportAsync();
|
||||||
|
await SeedAircraftAsync();
|
||||||
|
await SeedFlightAsync();
|
||||||
|
await SeedSeatAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedAirportAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports))
|
||||||
|
{
|
||||||
|
await flightDbContext.Airports.AddRangeAsync(InitialData.Airports);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Airport.InsertManyAsync(mapper.Map<List<AirportReadModel>>(InitialData.Airports));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedAircraftAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft))
|
||||||
|
{
|
||||||
|
await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map<List<AircraftReadModel>>(InitialData.Aircrafts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task SeedSeatAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats))
|
||||||
|
{
|
||||||
|
await flightDbContext.Seats.AddRangeAsync(InitialData.Seats);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Seat.InsertManyAsync(mapper.Map<List<SeatReadModel>>(InitialData.Seats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedFlightAsync()
|
||||||
|
{
|
||||||
|
if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights))
|
||||||
|
{
|
||||||
|
await flightDbContext.Flights.AddRangeAsync(InitialData.Flights);
|
||||||
|
await flightDbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable()))
|
||||||
|
{
|
||||||
|
await flightReadDbContext.Flight.InsertManyAsync(mapper.Map<List<FlightReadModel>>(InitialData.Flights));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.EFCore;
|
using BuildingBlocks.EFCore;
|
||||||
@ -6,27 +5,26 @@ using BuildingBlocks.HealthCheck;
|
|||||||
using BuildingBlocks.Logging;
|
using BuildingBlocks.Logging;
|
||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
|
using BuildingBlocks.OpenApi;
|
||||||
using BuildingBlocks.OpenTelemetry;
|
using BuildingBlocks.OpenTelemetry;
|
||||||
using BuildingBlocks.PersistMessageProcessor;
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using Identity.Configurations;
|
||||||
using Identity.Data;
|
using Identity.Data;
|
||||||
using Identity.Data.Seed;
|
using Identity.Data.Seed;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Prometheus;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Identity.Extensions.Infrastructure;
|
namespace Identity.Extensions.Infrastructure;
|
||||||
|
|
||||||
using BuildingBlocks.ProblemDetails;
|
|
||||||
using Configurations;
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
|
||||||
|
|
||||||
public static class InfrastructureExtensions
|
public static class InfrastructureExtensions
|
||||||
{
|
{
|
||||||
@ -67,7 +65,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddCustomDbContext<IdentityContext>();
|
builder.Services.AddCustomDbContext<IdentityContext>();
|
||||||
builder.Services.AddScoped<IDataSeeder, IdentityDataSeeder>();
|
builder.Services.AddScoped<IDataSeeder, IdentityDataSeeder>();
|
||||||
builder.AddCustomSerilog(env);
|
builder.AddCustomSerilog(env);
|
||||||
builder.Services.AddCustomSwagger(configuration, typeof(IdentityRoot).Assembly);
|
builder.Services.AddAspnetOpenApi();
|
||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly);
|
||||||
@ -104,8 +102,8 @@ public static class InfrastructureExtensions
|
|||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
});
|
});
|
||||||
app.UseMigration<IdentityContext>(env);
|
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
|
app.UseMigration<IdentityContext>();
|
||||||
app.UseCustomHealthCheck();
|
app.UseCustomHealthCheck();
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
|
|
||||||
@ -113,7 +111,7 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseCustomSwagger();
|
app.UseAspnetOpenApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||||
|
using BuildingBlocks.Core;
|
||||||
|
using BuildingBlocks.EFCore;
|
||||||
|
using Identity.Data.Seed;
|
||||||
|
using Identity.Identity.Constants;
|
||||||
|
using Identity.Identity.Models;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Integration.Test;
|
||||||
|
|
||||||
|
public class IdentityDataSeeder(
|
||||||
|
UserManager<User> userManager,
|
||||||
|
RoleManager<Role> roleManager,
|
||||||
|
IEventDispatcher eventDispatcher
|
||||||
|
)
|
||||||
|
: IDataSeeder
|
||||||
|
{
|
||||||
|
public async Task SeedAllAsync()
|
||||||
|
{
|
||||||
|
await SeedRoles();
|
||||||
|
await SeedUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedRoles()
|
||||||
|
{
|
||||||
|
if (await roleManager.RoleExistsAsync(Constants.Role.Admin) == false)
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(new Role { Name = Constants.Role.Admin });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await roleManager.RoleExistsAsync(Constants.Role.User) == false)
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(new Role { Name = Constants.Role.User });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SeedUsers()
|
||||||
|
{
|
||||||
|
if (await userManager.FindByNameAsync("samh") == null)
|
||||||
|
{
|
||||||
|
var result = await userManager.CreateAsync(InitialData.Users.First(), "Admin@123456");
|
||||||
|
|
||||||
|
if (result.Succeeded)
|
||||||
|
{
|
||||||
|
await userManager.AddToRoleAsync(InitialData.Users.First(), Constants.Role.Admin);
|
||||||
|
|
||||||
|
await eventDispatcher.SendAsync(new UserCreated(InitialData.Users.First().Id, InitialData.Users.First().FirstName + " " + InitialData.Users.First().LastName, InitialData.Users.First().PassPortNumber));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await userManager.FindByNameAsync("meysamh2") == null)
|
||||||
|
{
|
||||||
|
var result = await userManager.CreateAsync(InitialData.Users.Last(), "User@123456");
|
||||||
|
|
||||||
|
if (result.Succeeded)
|
||||||
|
{
|
||||||
|
await userManager.AddToRoleAsync(InitialData.Users.Last(), Constants.Role.User);
|
||||||
|
|
||||||
|
await eventDispatcher.SendAsync(new UserCreated(InitialData.Users.Last().Id, InitialData.Users.Last().FirstName + " " + InitialData.Users.Last().LastName, InitialData.Users.Last().PassPortNumber));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -8,9 +8,10 @@ using BuildingBlocks.Logging;
|
|||||||
using BuildingBlocks.Mapster;
|
using BuildingBlocks.Mapster;
|
||||||
using BuildingBlocks.MassTransit;
|
using BuildingBlocks.MassTransit;
|
||||||
using BuildingBlocks.Mongo;
|
using BuildingBlocks.Mongo;
|
||||||
|
using BuildingBlocks.OpenApi;
|
||||||
using BuildingBlocks.OpenTelemetry;
|
using BuildingBlocks.OpenTelemetry;
|
||||||
using BuildingBlocks.PersistMessageProcessor;
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
using BuildingBlocks.Swagger;
|
using BuildingBlocks.ProblemDetails;
|
||||||
using BuildingBlocks.Web;
|
using BuildingBlocks.Web;
|
||||||
using Figgle;
|
using Figgle;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
@ -25,7 +26,6 @@ using Serilog;
|
|||||||
|
|
||||||
namespace Passenger.Extensions.Infrastructure;
|
namespace Passenger.Extensions.Infrastructure;
|
||||||
|
|
||||||
using BuildingBlocks.ProblemDetails;
|
|
||||||
|
|
||||||
public static class InfrastructureExtensions
|
public static class InfrastructureExtensions
|
||||||
{
|
{
|
||||||
@ -67,7 +67,7 @@ public static class InfrastructureExtensions
|
|||||||
builder.AddCustomSerilog(env);
|
builder.AddCustomSerilog(env);
|
||||||
builder.Services.AddJwt();
|
builder.Services.AddJwt();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddCustomSwagger(configuration, typeof(PassengerRoot).Assembly);
|
builder.Services.AddAspnetOpenApi();
|
||||||
builder.Services.AddCustomVersioning();
|
builder.Services.AddCustomVersioning();
|
||||||
builder.Services.AddCustomMediatR();
|
builder.Services.AddCustomMediatR();
|
||||||
builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly);
|
builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly);
|
||||||
@ -98,15 +98,15 @@ public static class InfrastructureExtensions
|
|||||||
{
|
{
|
||||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||||
});
|
});
|
||||||
app.UseMigration<PassengerDbContext>(env);
|
|
||||||
app.UseCorrelationId();
|
app.UseCorrelationId();
|
||||||
|
app.UseMigration<PassengerDbContext>();
|
||||||
app.UseCustomHealthCheck();
|
app.UseCustomHealthCheck();
|
||||||
app.MapGrpcService<PassengerGrpcServices>();
|
app.MapGrpcService<PassengerGrpcServices>();
|
||||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseCustomSwagger();
|
app.UseAspnetOpenApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Grpc.AspNetCore" Version="2.65.0" />
|
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user