mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-05-04 20:10:54 +08:00
Merge pull request #310 from meysamhadeli/feat/update-dotnet-to-version-9
feat: update dotnet to version 9
This commit is contained in:
commit
b7fdbc22fa
@ -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
|
||||||
|
|||||||
6
.github/actions/build/action.yml
vendored
6
.github/actions/build/action.yml
vendored
@ -25,14 +25,16 @@ runs:
|
|||||||
# https://devblogs.microsoft.com/dotnet/dotnet-loves-github-actions/
|
# https://devblogs.microsoft.com/dotnet/dotnet-loves-github-actions/
|
||||||
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net#caching-dependencies
|
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net#caching-dependencies
|
||||||
- name: Cache NuGet Packages
|
- name: Cache NuGet Packages
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
if: success()
|
if: success()
|
||||||
with:
|
with:
|
||||||
path: ~/.nuget/packages
|
path: ~/.nuget/packages
|
||||||
key: ${{ runner.os }}-dotnet-nuget
|
key: ${{ runner.os }}-dotnet-nuget
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: '9.x.x'
|
||||||
|
|
||||||
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
|
# https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools
|
||||||
- name: Restore .NET Tools
|
- name: Restore .NET Tools
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build and Test Flight
|
- name: Build and Test Flight
|
||||||
uses: ./.github/actions/build-test
|
uses: ./.github/actions/build-test
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
27
README.md
27
README.md
@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
> 🚀 **A practical and imaginary microservices for implementing an infrastructure for up and running distributed system with the latest technology and architecture like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit in .Net 8.**
|
> 🚀 **A practical and imaginary microservices for implementing an infrastructure for up and running distributed system with the latest technology and architecture like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit in .Net 9.**
|
||||||
|
|
||||||
> 💡 **This project is not business-oriented and most of my focus was in the thechnical part for implement a distributed system with a sample project. In this project I implemented some concept in microservices like Messaging, Tracing, Event Driven Architecture, Vertical Slice Architecture, Event Sourcing, CQRS, DDD and gRpc.**
|
> 💡 **This project is not business-oriented and most of my focus was in the thechnical part for implement a distributed system with a sample project. In this project I implemented some concept in microservices like Messaging, Tracing, Event Driven Architecture, Vertical Slice Architecture, Event Sourcing, CQRS, DDD and gRpc.**
|
||||||
|
|
||||||
@ -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 9`](https://github.com/dotnet/aspnetcore)** - .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;
|
||||||
|
|||||||
@ -1,50 +1,39 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Transactions;
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
|
using BuildingBlocks.PersistMessageProcessor;
|
||||||
|
using BuildingBlocks.Polly;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
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.ExecuteSeedAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,4 +4,9 @@ namespace BuildingBlocks.EFCore
|
|||||||
{
|
{
|
||||||
Task SeedAllAsync();
|
Task SeedAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ITestDataSeeder
|
||||||
|
{
|
||||||
|
Task SeedAllAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/BuildingBlocks/EFCore/ISeedManager.cs
Normal file
7
src/BuildingBlocks/EFCore/ISeedManager.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
|
public interface ISeedManager
|
||||||
|
{
|
||||||
|
Task ExecuteSeedAsync();
|
||||||
|
Task ExecuteTestSeedAsync();
|
||||||
|
}
|
||||||
42
src/BuildingBlocks/EFCore/SeedManagers.cs
Normal file
42
src/BuildingBlocks/EFCore/SeedManagers.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BuildingBlocks.EFCore;
|
||||||
|
|
||||||
|
public class SeedManager(
|
||||||
|
ILogger<SeedManager> logger,
|
||||||
|
IWebHostEnvironment env,
|
||||||
|
IServiceProvider serviceProvider
|
||||||
|
) : ISeedManager
|
||||||
|
{
|
||||||
|
public async Task ExecuteSeedAsync()
|
||||||
|
{
|
||||||
|
if (!env.IsEnvironment("test"))
|
||||||
|
{
|
||||||
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
|
var dataSeeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
||||||
|
|
||||||
|
foreach (var seeder in dataSeeders)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name);
|
||||||
|
await seeder.SeedAllAsync();
|
||||||
|
logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteTestSeedAsync()
|
||||||
|
{
|
||||||
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
|
var testDataSeeders = scope.ServiceProvider.GetServices<ITestDataSeeder>();
|
||||||
|
|
||||||
|
foreach (var testSeeder in testDataSeeders)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Seed {SeederName} is started.", testSeeder.GetType().Name);
|
||||||
|
await testSeeder.SeedAllAsync();
|
||||||
|
logger.LogInformation("Seed {SeederName} is completed.", testSeeder.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;
|
||||||
@ -14,7 +12,7 @@ using Exception;
|
|||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddCustomMassTransit(this IServiceCollection services,
|
public static IServiceCollection AddCustomMassTransit(this IServiceCollection services,
|
||||||
IWebHostEnvironment env, Assembly assembly)
|
IWebHostEnvironment env, params Assembly[] assembly)
|
||||||
{
|
{
|
||||||
services.AddValidateOptions<RabbitMqOptions>();
|
services.AddValidateOptions<RabbitMqOptions>();
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ public static class Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void SetupMasstransitConfigurations(IServiceCollection services,
|
private static void SetupMasstransitConfigurations(IServiceCollection services,
|
||||||
IBusRegistrationConfigurator configure, Assembly assembly)
|
IBusRegistrationConfigurator configure, params Assembly[] assembly)
|
||||||
{
|
{
|
||||||
configure.AddConsumers(assembly);
|
configure.AddConsumers(assembly);
|
||||||
configure.AddSagaStateMachines(assembly);
|
configure.AddSagaStateMachines(assembly);
|
||||||
|
|||||||
@ -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,54 @@
|
|||||||
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(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,36 +5,25 @@ 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;
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
|
||||||
public PersistMessageBackgroundService(
|
|
||||||
ILogger<PersistMessageBackgroundService> logger,
|
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
IOptions<PersistMessageOptions> options)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
logger.LogInformation("PersistMessage Background Service Start");
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_options = options.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Task ExecuteAsync(CancellationToken stoppingToken)
|
await ProcessAsync(stoppingToken);
|
||||||
{
|
|
||||||
_logger.LogInformation("PersistMessage Background Service Start");
|
|
||||||
|
|
||||||
_executingTask = ProcessAsync(stoppingToken);
|
|
||||||
|
|
||||||
return _executingTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +32,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -10,7 +10,6 @@ using Microsoft.Extensions.Logging;
|
|||||||
namespace BuildingBlocks.PersistMessageProcessor;
|
namespace BuildingBlocks.PersistMessageProcessor;
|
||||||
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Polly;
|
|
||||||
|
|
||||||
public class PersistMessageProcessor : IPersistMessageProcessor
|
public class PersistMessageProcessor : IPersistMessageProcessor
|
||||||
{
|
{
|
||||||
@ -54,13 +53,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 +72,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 +108,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 +120,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 +192,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 +214,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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -97,6 +97,14 @@ where TEntryPoint : class
|
|||||||
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
services.ReplaceSingleton(AddHttpContextAccessorMock);
|
||||||
|
|
||||||
services.AddSingleton<PersistMessageBackgroundService>();
|
services.AddSingleton<PersistMessageBackgroundService>();
|
||||||
|
services.RemoveHostedService<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
|
||||||
@ -203,11 +211,7 @@ where TEntryPoint : class
|
|||||||
var published =
|
var published =
|
||||||
await TestHarness.Published.Any<TMessage>(cancellationToken);
|
await TestHarness.Published.Any<TMessage>(cancellationToken);
|
||||||
|
|
||||||
var faulty =
|
return published;
|
||||||
await TestHarness.Published.Any<Fault<TMessage>>(
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
return published && faulty == false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -224,10 +228,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;
|
||||||
@ -685,12 +686,8 @@ where TEntryPoint : class
|
|||||||
{
|
{
|
||||||
using var scope = Fixture.ServiceProvider.CreateScope();
|
using var scope = Fixture.ServiceProvider.CreateScope();
|
||||||
|
|
||||||
var seeders = scope.ServiceProvider.GetServices<IDataSeeder>();
|
var seedManager = scope.ServiceProvider.GetService<ISeedManager>();
|
||||||
|
await seedManager.ExecuteTestSeedAsync();
|
||||||
foreach (var seeder in seeders)
|
|
||||||
{
|
|
||||||
await seeder.SeedAllAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
@ -60,14 +58,14 @@ public static class InfrastructureExtensions
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddPersistMessageProcessor(env);
|
builder.Services.AddPersistMessageProcessor();
|
||||||
builder.Services.AddMongoDbContext<BookingReadDbContext>(configuration);
|
builder.Services.AddMongoDbContext<BookingReadDbContext>(configuration);
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
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 =>
|
||||||
{
|
{
|
||||||
@ -67,12 +67,12 @@ public static class InfrastructureExtensions
|
|||||||
builder.Services.AddCustomDbContext<FlightDbContext>();
|
builder.Services.AddCustomDbContext<FlightDbContext>();
|
||||||
builder.Services.AddScoped<IDataSeeder, FlightDataSeeder>();
|
builder.Services.AddScoped<IDataSeeder, FlightDataSeeder>();
|
||||||
builder.Services.AddMongoDbContext<FlightReadDbContext>(configuration);
|
builder.Services.AddMongoDbContext<FlightReadDbContext>(configuration);
|
||||||
builder.Services.AddPersistMessageProcessor(env);
|
builder.Services.AddPersistMessageProcessor();
|
||||||
|
|
||||||
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
|
||||||
{
|
{
|
||||||
@ -63,11 +61,11 @@ public static class InfrastructureExtensions
|
|||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddPersistMessageProcessor(env);
|
builder.Services.AddPersistMessageProcessor();
|
||||||
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 IdentityTestDataSeeder(
|
||||||
|
UserManager<User> userManager,
|
||||||
|
RoleManager<Role> roleManager,
|
||||||
|
IEventDispatcher eventDispatcher
|
||||||
|
)
|
||||||
|
: ITestDataSeeder
|
||||||
|
{
|
||||||
|
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
|
||||||
{
|
{
|
||||||
@ -60,14 +60,14 @@ public static class InfrastructureExtensions
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddPersistMessageProcessor(env);
|
builder.Services.AddPersistMessageProcessor();
|
||||||
builder.Services.AddCustomDbContext<PassengerDbContext>();
|
builder.Services.AddCustomDbContext<PassengerDbContext>();
|
||||||
builder.Services.AddMongoDbContext<PassengerReadDbContext>(configuration);
|
builder.Services.AddMongoDbContext<PassengerReadDbContext>(configuration);
|
||||||
|
|
||||||
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