mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-11 02:20:20 +08:00
feat: add modular monolith source
This commit is contained in:
parent
0d4dfb3459
commit
27d25aa47d
14
2-modular-monolith-architecture-style/src/Api/src/Api.csproj
Normal file
14
2-modular-monolith-architecture-style/src/Api/src/Api.csproj
Normal file
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Api</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Modules\Booking\src\Booking.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\Flight\src\Flight.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\Identity\src\Identity.csproj" />
|
||||
<ProjectReference Include="..\..\Modules\Passenger\src\Passenger.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,122 @@
|
||||
using System.Threading.RateLimiting;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Exception;
|
||||
using BuildingBlocks.HealthCheck;
|
||||
using BuildingBlocks.Jwt;
|
||||
using BuildingBlocks.Logging;
|
||||
using BuildingBlocks.Mapster;
|
||||
using BuildingBlocks.MassTransit;
|
||||
using BuildingBlocks.OpenApi;
|
||||
using BuildingBlocks.OpenTelemetryCollector;
|
||||
using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.ProblemDetails;
|
||||
using BuildingBlocks.Web;
|
||||
using Figgle;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
|
||||
namespace Api.Extensions;
|
||||
|
||||
public static class SharedInfrastructureExtensions
|
||||
{
|
||||
public static WebApplicationBuilder AddSharedInfrastructure(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Host.UseDefaultServiceProvider(
|
||||
(context, options) =>
|
||||
{
|
||||
// Service provider validation
|
||||
// ref: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
|
||||
options.ValidateScopes = context.HostingEnvironment.IsDevelopment() ||
|
||||
context.HostingEnvironment.IsStaging() ||
|
||||
context.HostingEnvironment.IsEnvironment("tests");
|
||||
|
||||
options.ValidateOnBuild = true;
|
||||
});
|
||||
|
||||
var appOptions = builder.Services.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
Console.WriteLine(FiggleFonts.Standard.Render(appOptions.Name));
|
||||
|
||||
builder.AddCustomSerilog(builder.Environment);
|
||||
builder.Services.AddScoped<ICurrentUserProvider, CurrentUserProvider>();
|
||||
builder.Services.AddJwt();
|
||||
builder.Services.AddTransient<AuthHeaderHandler>();
|
||||
builder.Services.AddPersistMessageProcessor();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddAspnetOpenApi();
|
||||
builder.Services.AddCustomVersioning();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<IEventDispatcher, EventDispatcher>();
|
||||
|
||||
builder.Services.AddCustomMassTransit(
|
||||
builder.Environment,
|
||||
TransportType.InMemory,
|
||||
AppDomain.CurrentDomain.GetAssemblies());
|
||||
|
||||
builder.Services.Configure<ApiBehaviorOptions>(
|
||||
options => options.SuppressModelStateInvalidFilter = true);
|
||||
|
||||
builder.Services.AddRateLimiter(
|
||||
options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
|
||||
httpContext =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(
|
||||
partitionKey: httpContext.User.Identity?.Name ??
|
||||
httpContext.Request.Headers.Host.ToString(),
|
||||
factory: partition => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
AutoReplenishment = true,
|
||||
PermitLimit = 10,
|
||||
QueueLimit = 0,
|
||||
Window = TimeSpan.FromMinutes(1)
|
||||
}));
|
||||
});
|
||||
|
||||
builder.AddCustomObservability();
|
||||
builder.Services.AddCustomHealthCheck();
|
||||
|
||||
builder.Services.AddGrpc(
|
||||
options =>
|
||||
{
|
||||
options.Interceptors.Add<GrpcExceptionInterceptor>();
|
||||
});
|
||||
|
||||
builder.Services.AddEasyCaching(options => { options.UseInMemory(builder.Configuration, "mem"); });
|
||||
|
||||
builder.Services.AddValidatorsFromAssemblies(AppDomain.CurrentDomain.GetAssemblies());
|
||||
builder.Services.AddCustomMapster(AppDomain.CurrentDomain.GetAssemblies());
|
||||
builder.Services.AddProblemDetails();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public static WebApplication UserSharedInfrastructure(this WebApplication app)
|
||||
{
|
||||
var appOptions = app.Configuration.GetOptions<AppOptions>(nameof(AppOptions));
|
||||
|
||||
app.UseCustomProblemDetails();
|
||||
app.UseCustomObservability();
|
||||
app.UseCustomHealthCheck();
|
||||
|
||||
app.UseSerilogRequestLogging(
|
||||
options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
|
||||
});
|
||||
|
||||
app.UseCorrelationId();
|
||||
app.UseRateLimiter();
|
||||
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseAspnetOpenApi();
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
41
2-modular-monolith-architecture-style/src/Api/src/Program.cs
Normal file
41
2-modular-monolith-architecture-style/src/Api/src/Program.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using Api.Extensions;
|
||||
using Booking.Extensions.Infrastructure;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Extensions.Infrastructure;
|
||||
using Identity.Extensions.Infrastructure;
|
||||
using Passenger.Extensions.Infrastructure;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddMinimalEndpoints(assemblies: AppDomain.CurrentDomain.GetAssemblies());
|
||||
|
||||
builder.AddSharedInfrastructure();
|
||||
|
||||
builder.AddFlightModules();
|
||||
builder.AddIdentityModules();
|
||||
builder.AddPassengerModules();
|
||||
builder.AddBookingModules();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// ref: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-7.0#routing-basics
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapMinimalEndpoints();
|
||||
app.UserSharedInfrastructure();
|
||||
|
||||
app.UseFlightModules();
|
||||
app.UseIdentityModules();
|
||||
app.UsePassengerModules();
|
||||
app.UseBookingModules();
|
||||
|
||||
|
||||
app.Run();
|
||||
|
||||
namespace Api
|
||||
{
|
||||
public partial class Program
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:17191",
|
||||
"sslPort": 44352
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Api": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchUrl": "swagger",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:5000;http://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
{
|
||||
"AppOptions": {
|
||||
"Name": "Booking-Modular-Monolith"
|
||||
},
|
||||
"LogOptions": {
|
||||
"Level": "information",
|
||||
"LogTemplate": "{Timestamp:HH:mm:ss} [{Level:u4}] {Message:lj}{NewLine}{Exception}",
|
||||
"File": {
|
||||
"Enabled": false,
|
||||
"Path": "logs/logs.txt",
|
||||
"Interval": "day"
|
||||
}
|
||||
},
|
||||
"PostgresOptions": {
|
||||
"ConnectionString": {
|
||||
"Flight": "Server=localhost;Port=5432;Database=flight;User Id=postgres;Password=postgres;Include Error Detail=true",
|
||||
"Identity": "Server=localhost;Port=5432;Database=identity;User Id=postgres;Password=postgres;Include Error Detail=true",
|
||||
"Passenger": "Server=localhost;Port=5432;Database=passenger;User Id=postgres;Password=postgres;Include Error Detail=true"
|
||||
}
|
||||
},
|
||||
"MongoOptions": {
|
||||
"ConnectionString": "mongodb://localhost:27017",
|
||||
"DatabaseName": "booking-read"
|
||||
},
|
||||
"EventStoreOptions": {
|
||||
"ConnectionString": "esdb://localhost:2113?tls=false"
|
||||
},
|
||||
"Jwt": {
|
||||
"Authority": "http://localhost:6005",
|
||||
"Audience": "flight-api",
|
||||
"RequireHttpsMetadata": false,
|
||||
"MetadataAddress": "http://localhost:6005/.well-known/openid-configuration"
|
||||
},
|
||||
"RabbitMqOptions": {
|
||||
"HostName": "localhost",
|
||||
"ExchangeName": "flight",
|
||||
"UserName": "guest",
|
||||
"Password": "guest",
|
||||
"Port": 5672
|
||||
},
|
||||
"PersistMessageOptions": {
|
||||
"Interval": 30,
|
||||
"Enabled": true,
|
||||
"ConnectionString": "Server=localhost;Port=5432;Database=persist_message;User Id=postgres;Password=postgres;Include Error Detail=true"
|
||||
},
|
||||
"HealthOptions": {
|
||||
"Enabled": false
|
||||
},
|
||||
"ObservabilityOptions": {
|
||||
"InstrumentationName": "flight_service",
|
||||
"OTLPOptions": {
|
||||
"OTLPGrpExporterEndpoint": "http://localhost:4317"
|
||||
},
|
||||
"AspireDashboardOTLPOptions": {
|
||||
"OTLPGrpExporterEndpoint": "http://localhost:4319"
|
||||
},
|
||||
"ZipkinOptions": {
|
||||
"HttpExporterEndpoint": "http://localhost:9411/api/v2/spans"
|
||||
},
|
||||
"JaegerOptions": {
|
||||
"OTLPGrpcExporterEndpoint": "http://localhost:14317",
|
||||
"HttpExporterEndpoint": "http://localhost:14268/api/traces"
|
||||
},
|
||||
"UsePrometheusExporter": true,
|
||||
"UseOTLPExporter": true,
|
||||
"UseAspireOTLPExporter": true,
|
||||
"UseGrafanaExporter": false,
|
||||
"ServiceName": "Flight Service"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
{"Version":1,"Id":"73D9025BDA857BF270C99C6594EE4246","Created":"2024-09-02T18:34:53.8631045Z","Algorithm":"RS256","IsX509Certificate":false,"Data":"CfDJ8MEQ06y_es9CrKY4Ou9Uc7Hf97ujXcMcuawi9V5VrBvxWbAqbdjBlk5zKK_NzrDURTwaFvgOfLUQ__0r-sMvtTXBZLhWhNWJnLn1-KWhtnpvPt5jFTAf2f5mvrjVTTN8E-NTGMGk2yLOYm2TVE3QL7DuLRrazydXLJvIB71O2d_OrAfB7Pq62xrnwLgp4BErb_HphYUExsAEp7jn6uL34QJ5HI6zsb_ct2SHvOl0CzUH2yIjjrY_u-5GAgmqqpUyysGwUh8RpR_CDPCd8ZxgSOiKAlBkp3kQXxf86MF0C-kfBKU-iflrJRSJ1-5R8F5nNPK8FL29I4i7SYCugIkLvqez3wTewPUFv00bTpcfs3V_tmfqBFLyNWm4sppzpU9HGB3elZ0z5bBa_IGbtIrlHC_o4SDSxKKdD4OUZHGLE5X--xWBw5uV9GSgrSH6bcnHZ6rLd3qrt7b82BMr-rVIGyFzGIE6OTLVGkXSL6FdpJ9Lezp06qnZ6DtLFI86lVJGsYIuXR_AIVrz9U8uqCrK6jLwGCk9nR0adPtAUJfgcXGVVTQlvt_YgZv5k5_rYcVl0yLNdgd-BoidXeoqLPJxIJOCohwumVcqTPkf-gB_hjgNk6IEqXZxm0Tzl0d2jpyERDHdHxIvS5o1h4YUfkiMcliRfQMNkLoDxf0H2hFNXRYQKkQ1y6cwS9juq66Uj_-v-vN1etE-hK45ULFCfyppBvb2aTYfTuce5S0ps1t0ZIjvpm7Kvjg3doHEi97N-IqxYaf8r6n8gBcaUwlrfcHaYNPLRyWywX_varEmLm9qGK5KJj2itGNfw0wU0EygIeoV6V_PzqyAkf0JcVTC7lcog37TPdNU2AGzH0S8oXiAQEd49wPs2ZApjiOaiz26efr03I-hD5N91-R2_9ACGjENGPVHMyUtMVV5RPs3-pQMv9f_zweOuLQo7ZfhScqu4HmxGW70amuV4anMxGCQzbi3JWnkTmspptzClJyvE_MJVSTQ6SX04DaS4buSG3wZEc9Qy9SqTj-9CJ7XFGFs8XYmKUj_cIoQF1XuoSnWblsnnEC7EbNRF7y9fG8ZG-Sk3TEEYnalxRrcS-i26wVNdnuUBEmidz_HfsxFxCDKKmx7GHTvHxy72kI84ucMeQeVFjJI3ZDynGn--cL9xBiUbUKM8WDhJ-AgZ76wwh0qAPw5xJ6yHi-15moxySUkvFLjlNkP2Ad5j3_3ab_r6VIunM6zhsq7pSBWIg5povuV5ZwNVZQX0IeLqV9bHug443LaK5a57dTK8wy346AFftV-wc71i4Nt5MIFcOs3lxRPqYij1enbrPYvIV4-N8Sy6aaYj25Qn7VHrGeW72aZPAYY5W-czoPw_Oo6xYGjaPYFFsUSZVg6IQwCzwwAxUoc2gAL33FJQTNvNSnrYBJ5HN-Tqan23Pw_bEus7HHZu2N1daFjqtrl4-oOco46phsppUjH3LGhOPJnFSChr-W8tlk80coJ8IK_AsGludKB09WzId9JBtI5cp3Yu1J7N6nSL7nVTrT6Gw_0hitSoeu5ZLPSS9ooAynAXrvB_s0l0L9aFTRuc5IEhgt4bLzbeqimfQemRlBsNz09JGe04gmOOCmjWD52JHWUiVJQNMavrSGtW9Dy1-Z5h0D_BHzhpTia1S7wx7dSdItJ0-Pm1Au_TNkQGm4ffNFsVDQmNkCYyc8yFnYmZMYYEaPmbw7DvQTs1MHoe7aUzMM0DKcaqboSaxqQK9sxymgElvdoYqOMRWzS7s39UQ1O4TfPngfrWdtN2DogGUtyS-vPfNJpdS6jZvJAj8czgl7PU8buwWyPApE1-FVL32wC6a8dkHvJi4p7fbBjmTfFCnuW8G1KBX3VuToctJvidSjzoSUTX3vgKVni2QW-55Sh7DUYy91FGXGB_ui1yuxEnLmymtbWokcWYkIwcAsl8im70V4oK63ypNSYWea_gaDWMFXT_vANB1iAkr-_zE_ECocOXo93QqSR5UdLmfQFvLiDwjUeovkjFS5C2Z8AjEvHvFkedGWOIK5Bpam-0IEFip3Fvg6RgxwTinFXXa8PiRkcLSlt0J81b85ybrKsDj5WtUA-MFuK2Silyofn9BgD_lh9RU4HPFhVoqey7AuEJjHtGvqz4EnE_05y4A_mKgvJBAs4QiYjCopWtheeOGeeoUa636Ewmu30P66C5mimdAIx36-55xlyJBIM7DFFM6RAGvfAmpyNphjwT0y84B4pOhFEZeOQ2me2sfG-xRJbjjgDhP2SwBBEQ-hCLGeqOD-Xo74FZC4lCTvtn2Sbu1kIw0kz2P_vrq6d6SZwEIrhWYhfRVKTrT8nXj8i48Jdc1d1fyKdRL15USgLhAT-QSNcgVYHRLsVlQx5-b51tGg6Atx6vGCxtXBRSaTwZ3IxbdJs0T62H14K5U81EFu-2Vvf-cMwCm4gCQATxvvAsqToxElou9ZjIVMPt_FQUyAMtJke","DataProtected":true}
|
||||
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="GrpcClient\Protos\flight.proto" GrpcServices="Both" />
|
||||
<Protobuf Include="GrpcClient\Protos\passenger.proto" GrpcServices="Both" />
|
||||
<Folder Include="GrpcClient\Protos" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\building-blocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,4 @@
|
||||
namespace Booking.Booking.Dtos;
|
||||
|
||||
public record BookingResponseDto(Guid Id, string Name, string FlightNumber, Guid AircraftId, decimal Price,
|
||||
DateTime FlightDate, string SeatNumber, Guid DepartureAirportId, Guid ArriveAirportId, string Description);
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class BookingAlreadyExistException : ConflictException
|
||||
{
|
||||
public BookingAlreadyExistException(int? code = default) : base("Booking already exist!", code)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class FlightNotFoundException : NotFoundException
|
||||
{
|
||||
public FlightNotFoundException() : base("Flight doesn't exist!")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidAircraftIdException : BadRequestException
|
||||
{
|
||||
public InvalidAircraftIdException(Guid aircraftId)
|
||||
: base($"aircraftId: '{aircraftId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidArriveAirportIdException : BadRequestException
|
||||
{
|
||||
public InvalidArriveAirportIdException(Guid arriveAirportId)
|
||||
: base($"arriveAirportId: '{arriveAirportId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidDepartureAirportIdException : BadRequestException
|
||||
{
|
||||
public InvalidDepartureAirportIdException(Guid departureAirportId)
|
||||
: base($"departureAirportId: '{departureAirportId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidFlightDateException : BadRequestException
|
||||
{
|
||||
public InvalidFlightDateException(DateTime flightDate)
|
||||
: base($"Flight Date: '{flightDate}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
public class InvalidFlightNumberException : BadRequestException
|
||||
{
|
||||
public InvalidFlightNumberException(string flightNumber)
|
||||
: base($"Flight Number: '{flightNumber}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidPassengerNameException : BadRequestException
|
||||
{
|
||||
public InvalidPassengerNameException(string passengerName)
|
||||
: base($"Passenger Name: '{passengerName}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidPriceException : BadRequestException
|
||||
{
|
||||
public InvalidPriceException(decimal price)
|
||||
: base($"Price: '{price}' must be grater than or equal 0.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class SeatNumberException : BadRequestException
|
||||
{
|
||||
public SeatNumberException(string seatNumber)
|
||||
: base($"Seat Number: '{seatNumber}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
using Booking.Booking.Dtos;
|
||||
using Mapster;
|
||||
|
||||
namespace Booking.Booking.Features;
|
||||
|
||||
using CreatingBook.V1;
|
||||
|
||||
public class BookingMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible);
|
||||
|
||||
config.NewConfig<Models.Booking, BookingResponseDto>()
|
||||
.ConstructUsing(x => new BookingResponseDto(x.Id, x.PassengerInfo.Name, x.Trip.FlightNumber,
|
||||
x.Trip.AircraftId, x.Trip.Price, x.Trip.FlightDate, x.Trip.SeatNumber, x.Trip.DepartureAirportId, x.Trip.ArriveAirportId,
|
||||
x.Trip.Description));
|
||||
|
||||
|
||||
config.NewConfig<CreateBookingRequestDto, CreateBooking>()
|
||||
.ConstructUsing(x => new CreateBooking(x.PassengerId, x.FlightId, x.Description));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
namespace Booking.Booking.Features.CreatingBook.V1;
|
||||
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Core.Model;
|
||||
using BuildingBlocks.EventStoreDB.Repository;
|
||||
using BuildingBlocks.Web;
|
||||
using Duende.IdentityServer.EntityFramework.Entities;
|
||||
using Exceptions;
|
||||
using Flight;
|
||||
using FluentValidation;
|
||||
using Mapster;
|
||||
using MapsterMapper;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Passenger;
|
||||
using ValueObjects;
|
||||
|
||||
public record CreateBooking(Guid PassengerId, Guid FlightId, string Description) : ICommand<CreateBookingResult>
|
||||
{
|
||||
public Guid Id { get; init; } = NewId.NextGuid();
|
||||
}
|
||||
|
||||
public record CreateBookingResult(ulong Id);
|
||||
|
||||
public record BookingCreatedDomainEvent(Guid Id, PassengerInfo PassengerInfo, Trip Trip) : Entity<Guid>, IDomainEvent;
|
||||
|
||||
public record CreateBookingRequestDto(Guid PassengerId, Guid FlightId, string Description);
|
||||
|
||||
public record CreateBookingResponseDto(ulong Id);
|
||||
|
||||
public class CreateBookingEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/booking", async (CreateBookingRequestDto request,
|
||||
IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var command = mapper.Map<CreateBooking>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
var response = result.Adapt<CreateBookingResponseDto>();
|
||||
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.RequireAuthorization(nameof(ApiScope))
|
||||
.WithName("CreateBooking")
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Booking").Build())
|
||||
.Produces<CreateBookingResponseDto>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.WithSummary("Create Booking")
|
||||
.WithDescription("Create Booking")
|
||||
.WithOpenApi()
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateBookingValidator : AbstractValidator<CreateBooking>
|
||||
{
|
||||
public CreateBookingValidator()
|
||||
{
|
||||
RuleFor(x => x.FlightId).NotNull().WithMessage("FlightId is required!");
|
||||
RuleFor(x => x.PassengerId).NotNull().WithMessage("PassengerId is required!");
|
||||
}
|
||||
}
|
||||
|
||||
internal class CreateBookingCommandHandler : ICommandHandler<CreateBooking, CreateBookingResult>
|
||||
{
|
||||
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
|
||||
private readonly ICurrentUserProvider _currentUserProvider;
|
||||
private readonly IEventDispatcher _eventDispatcher;
|
||||
private readonly FlightGrpcService.FlightGrpcServiceClient _flightGrpcServiceClient;
|
||||
private readonly PassengerGrpcService.PassengerGrpcServiceClient _passengerGrpcServiceClient;
|
||||
|
||||
public CreateBookingCommandHandler(IEventStoreDBRepository<Models.Booking> eventStoreDbRepository,
|
||||
ICurrentUserProvider currentUserProvider,
|
||||
IEventDispatcher eventDispatcher,
|
||||
FlightGrpcService.FlightGrpcServiceClient flightGrpcServiceClient,
|
||||
PassengerGrpcService.PassengerGrpcServiceClient passengerGrpcServiceClient)
|
||||
{
|
||||
_eventStoreDbRepository = eventStoreDbRepository;
|
||||
_currentUserProvider = currentUserProvider;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_flightGrpcServiceClient = flightGrpcServiceClient;
|
||||
_passengerGrpcServiceClient = passengerGrpcServiceClient;
|
||||
}
|
||||
|
||||
public async Task<CreateBookingResult> Handle(CreateBooking command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var flight =
|
||||
await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest { Id = command.FlightId.ToString() }, cancellationToken: cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFoundException();
|
||||
}
|
||||
|
||||
var passenger =
|
||||
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest { Id = command.PassengerId.ToString() }, cancellationToken: cancellationToken);
|
||||
|
||||
var emptySeat = (await _flightGrpcServiceClient
|
||||
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest { FlightId = command.FlightId.ToString() }, cancellationToken: cancellationToken)
|
||||
.ResponseAsync)
|
||||
?.SeatDtos?.FirstOrDefault();
|
||||
|
||||
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
|
||||
|
||||
if (reservation is not null && !reservation.IsDeleted)
|
||||
{
|
||||
throw new BookingAlreadyExistException();
|
||||
}
|
||||
|
||||
var aggrigate = Models.Booking.Create(command.Id, PassengerInfo.Of(passenger.PassengerDto?.Name), Trip.Of(
|
||||
flight.FlightDto.FlightNumber, new Guid(flight.FlightDto.AircraftId),
|
||||
new Guid(flight.FlightDto.DepartureAirportId),
|
||||
new Guid(flight.FlightDto.ArriveAirportId), flight.FlightDto.FlightDate.ToDateTime(),
|
||||
(decimal)flight.FlightDto.Price, command.Description,
|
||||
emptySeat?.SeatNumber),
|
||||
false, _currentUserProvider.GetCurrentUserId());
|
||||
|
||||
await _eventDispatcher.SendAsync(aggrigate.DomainEvents, cancellationToken: cancellationToken);
|
||||
|
||||
await _flightGrpcServiceClient.ReserveSeatAsync(new ReserveSeatRequest
|
||||
{
|
||||
FlightId = flight.FlightDto.Id,
|
||||
SeatNumber = emptySeat?.SeatNumber
|
||||
}, cancellationToken: cancellationToken);
|
||||
|
||||
var result = await _eventStoreDbRepository.Add(
|
||||
aggrigate,
|
||||
cancellationToken);
|
||||
|
||||
return new CreateBookingResult(result);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
using BuildingBlocks.EventStoreDB.Events;
|
||||
|
||||
namespace Booking.Booking.Models;
|
||||
|
||||
using Features.CreatingBook.V1;
|
||||
using ValueObjects;
|
||||
|
||||
public record Booking : AggregateEventSourcing<Guid>
|
||||
{
|
||||
public Trip Trip { get; private set; }
|
||||
public PassengerInfo PassengerInfo { get; private set; }
|
||||
|
||||
public static Booking Create(Guid id, PassengerInfo passengerInfo, Trip trip, bool isDeleted = false, long? userId = null)
|
||||
{
|
||||
var booking = new Booking { Id = id, Trip = trip, PassengerInfo = passengerInfo, IsDeleted = isDeleted };
|
||||
|
||||
var @event = new BookingCreatedDomainEvent(booking.Id, booking.PassengerInfo, booking.Trip)
|
||||
{
|
||||
IsDeleted = booking.IsDeleted,
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatedBy = userId
|
||||
};
|
||||
|
||||
booking.AddDomainEvent(@event);
|
||||
booking.Apply(@event);
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
public override void When(object @event)
|
||||
{
|
||||
switch (@event)
|
||||
{
|
||||
case BookingCreatedDomainEvent bookingCreated:
|
||||
{
|
||||
Apply(bookingCreated);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Apply(BookingCreatedDomainEvent @event)
|
||||
{
|
||||
Id = @event.Id;
|
||||
Trip = @event.Trip;
|
||||
PassengerInfo = @event.PassengerInfo;
|
||||
IsDeleted = @event.IsDeleted;
|
||||
Version++;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Booking.Booking.Models;
|
||||
|
||||
using ValueObjects;
|
||||
|
||||
public class BookingReadModel
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required Guid BookId { get; init; }
|
||||
public required Trip Trip { get; init; }
|
||||
public required PassengerInfo PassengerInfo { get; init; }
|
||||
public required bool IsDeleted { get; init; }
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
namespace Booking.Booking.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record PassengerInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
private PassengerInfo(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public static PassengerInfo Of(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new InvalidPassengerNameException(name);
|
||||
}
|
||||
|
||||
return new PassengerInfo(name);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
namespace Booking.Booking.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record Trip
|
||||
{
|
||||
public string FlightNumber { get; }
|
||||
public Guid AircraftId { get; }
|
||||
public Guid DepartureAirportId { get; }
|
||||
public Guid ArriveAirportId { get; }
|
||||
public DateTime FlightDate { get; }
|
||||
public decimal Price { get; }
|
||||
public string Description { get; }
|
||||
public string SeatNumber { get; }
|
||||
|
||||
private Trip(string flightNumber, Guid aircraftId, Guid departureAirportId, Guid arriveAirportId,
|
||||
DateTime flightDate, decimal price, string description, string seatNumber)
|
||||
{
|
||||
FlightNumber = flightNumber;
|
||||
AircraftId = aircraftId;
|
||||
DepartureAirportId = departureAirportId;
|
||||
ArriveAirportId = arriveAirportId;
|
||||
FlightDate = flightDate;
|
||||
Price = price;
|
||||
Description = description;
|
||||
SeatNumber = seatNumber;
|
||||
}
|
||||
|
||||
public static Trip Of(string flightNumber, Guid aircraftId, Guid departureAirportId, Guid arriveAirportId,
|
||||
DateTime flightDate, decimal price, string description, string seatNumber)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(flightNumber))
|
||||
{
|
||||
throw new InvalidFlightNumberException(flightNumber);
|
||||
}
|
||||
|
||||
if (aircraftId == Guid.Empty)
|
||||
{
|
||||
throw new InvalidAircraftIdException(aircraftId);
|
||||
}
|
||||
|
||||
if (departureAirportId == Guid.Empty)
|
||||
{
|
||||
throw new InvalidDepartureAirportIdException(departureAirportId);
|
||||
}
|
||||
|
||||
if (arriveAirportId == Guid.Empty)
|
||||
{
|
||||
throw new InvalidArriveAirportIdException(departureAirportId);
|
||||
}
|
||||
|
||||
if (flightDate == default)
|
||||
{
|
||||
throw new InvalidFlightDateException(flightDate);
|
||||
}
|
||||
|
||||
if (price < 0)
|
||||
{
|
||||
throw new InvalidPriceException(price);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(seatNumber))
|
||||
{
|
||||
throw new SeatNumberException(seatNumber);
|
||||
}
|
||||
|
||||
return new Trip(flightNumber, aircraftId, departureAirportId, arriveAirportId, flightDate, price, description, seatNumber);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.EventStoreDB.Events;
|
||||
using BuildingBlocks.EventStoreDB.Projections;
|
||||
using MediatR;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Booking;
|
||||
|
||||
using Booking.Features.CreatingBook.V1;
|
||||
using Booking.Models;
|
||||
using MassTransit;
|
||||
|
||||
public class BookingProjection : IProjectionProcessor
|
||||
{
|
||||
private readonly BookingReadDbContext _bookingReadDbContext;
|
||||
|
||||
public BookingProjection(BookingReadDbContext bookingReadDbContext)
|
||||
{
|
||||
_bookingReadDbContext = bookingReadDbContext;
|
||||
}
|
||||
|
||||
public async Task ProcessEventAsync<T>(StreamEvent<T> streamEvent, CancellationToken cancellationToken = default)
|
||||
where T : INotification
|
||||
{
|
||||
switch (streamEvent.Data)
|
||||
{
|
||||
case BookingCreatedDomainEvent bookingCreatedDomainEvent:
|
||||
await Apply(bookingCreatedDomainEvent, cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Apply(BookingCreatedDomainEvent @event, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var reservation =
|
||||
await _bookingReadDbContext.Booking.AsQueryable().SingleOrDefaultAsync(x => x.Id == @event.Id && !x.IsDeleted,
|
||||
cancellationToken);
|
||||
|
||||
if (reservation == null)
|
||||
{
|
||||
var bookingReadModel = new BookingReadModel
|
||||
{
|
||||
Id = NewId.NextGuid(),
|
||||
Trip = @event.Trip,
|
||||
BookId = @event.Id,
|
||||
PassengerInfo = @event.PassengerInfo,
|
||||
IsDeleted = @event.IsDeleted
|
||||
};
|
||||
|
||||
await _bookingReadDbContext.Booking.InsertOneAsync(bookingReadModel, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Booking;
|
||||
|
||||
public class BookingRoot
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Booking.Configuration;
|
||||
|
||||
public class GrpcOptions
|
||||
{
|
||||
public string FlightAddress { get; set; }
|
||||
public string PassengerAddress { get; set; }
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using BuildingBlocks.Mongo;
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Booking.Data;
|
||||
|
||||
using Booking.Models;
|
||||
|
||||
public class BookingReadDbContext : MongoDbContext
|
||||
{
|
||||
public BookingReadDbContext(IOptions<MongoOptions> options) : base(options)
|
||||
{
|
||||
Booking = GetCollection<BookingReadModel>(nameof(Booking).Underscore());
|
||||
}
|
||||
|
||||
public IMongoCollection<BookingReadModel> Booking { get; }
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Booking;
|
||||
|
||||
using Booking.Features.CreatingBook.V1;
|
||||
|
||||
public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
public IIntegrationEvent? MapToIntegrationEvent(IDomainEvent @event)
|
||||
{
|
||||
return @event switch
|
||||
{
|
||||
BookingCreatedDomainEvent e => new BookingCreated(e.Id),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public IInternalCommand? MapToInternalCommand(IDomainEvent @event)
|
||||
{
|
||||
return @event switch
|
||||
{
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
using Booking.Configuration;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Passenger;
|
||||
|
||||
namespace Booking.Extensions.Infrastructure;
|
||||
|
||||
using BuildingBlocks.Polly;
|
||||
|
||||
public static class GrpcClientExtensions
|
||||
{
|
||||
public static IServiceCollection AddGrpcClients(this IServiceCollection services)
|
||||
{
|
||||
var grpcOptions = services.GetOptions<GrpcOptions>("Grpc");
|
||||
|
||||
services.AddGrpcClient<FlightGrpcService.FlightGrpcServiceClient>(o =>
|
||||
{
|
||||
o.Address = new Uri(grpcOptions.FlightAddress);
|
||||
})
|
||||
.AddGrpcRetryPolicyHandler()
|
||||
.AddGrpcCircuitBreakerPolicyHandler();
|
||||
|
||||
services.AddGrpcClient<PassengerGrpcService.PassengerGrpcServiceClient>(o =>
|
||||
{
|
||||
o.Address = new Uri(grpcOptions.PassengerAddress);
|
||||
})
|
||||
.AddGrpcRetryPolicyHandler()
|
||||
.AddGrpcCircuitBreakerPolicyHandler();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.EventStoreDB;
|
||||
using BuildingBlocks.Mongo;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Booking.Extensions.Infrastructure;
|
||||
|
||||
public static class InfrastructureExtensions
|
||||
{
|
||||
public static WebApplicationBuilder AddBookingModules(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddScoped<IEventMapper, EventMapper>();
|
||||
builder.AddMongoDbContext<BookingReadDbContext>();
|
||||
|
||||
// ref: https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/ECommerce
|
||||
builder.Services.AddEventStore(builder.Configuration, typeof(BookingRoot).Assembly)
|
||||
.AddEventStoreDBSubscriptionToAll();
|
||||
|
||||
builder.Services.AddGrpcClients();
|
||||
|
||||
builder.Services.AddCustomMediatR();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public static WebApplication UseBookingModules(this WebApplication app)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Logging;
|
||||
using BuildingBlocks.Validation;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Booking.Extensions.Infrastructure;
|
||||
|
||||
public static class MediatRExtensions
|
||||
{
|
||||
public static IServiceCollection AddCustomMediatR(this IServiceCollection services)
|
||||
{
|
||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(typeof(BookingRoot).Assembly));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CachingBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InvalidateCachingBehavior<,>));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package flight;
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
service FlightGrpcService {
|
||||
|
||||
rpc GetById (GetByIdRequest) returns (GetFlightByIdResult);
|
||||
rpc GetAvailableSeats (GetAvailableSeatsRequest) returns (GetAvailableSeatsResult);
|
||||
rpc ReserveSeat (ReserveSeatRequest) returns (ReserveSeatResult);
|
||||
}
|
||||
|
||||
message GetByIdRequest {
|
||||
string Id = 1;
|
||||
}
|
||||
|
||||
message GetFlightByIdResult{
|
||||
FlightResponse FlightDto = 1;
|
||||
}
|
||||
|
||||
message GetAvailableSeatsResult{
|
||||
repeated SeatDtoResponse SeatDtos = 1;
|
||||
}
|
||||
|
||||
message ReserveSeatResult{
|
||||
string Id = 1;
|
||||
}
|
||||
|
||||
message FlightResponse {
|
||||
string Id = 1;
|
||||
string FlightNumber = 2;
|
||||
string AircraftId = 3;
|
||||
string DepartureAirportId = 4;
|
||||
google.protobuf.Timestamp DepartureDate = 5;
|
||||
google.protobuf.Timestamp ArriveDate = 6;
|
||||
string ArriveAirportId = 7;
|
||||
double DurationMinutes = 8;
|
||||
google.protobuf.Timestamp FlightDate = 9;
|
||||
FlightStatus Status = 10;
|
||||
double Price = 11;
|
||||
string FlightId = 12;
|
||||
}
|
||||
|
||||
message GetAvailableSeatsRequest {
|
||||
string FlightId = 1;
|
||||
}
|
||||
|
||||
message SeatDtoResponse {
|
||||
string Id = 1;
|
||||
string SeatNumber = 2;
|
||||
SeatType Type = 3;
|
||||
SeatClass Class = 4;
|
||||
string FlightId = 5;
|
||||
}
|
||||
|
||||
|
||||
message ReserveSeatRequest {
|
||||
string FlightId = 1;
|
||||
string SeatNumber = 2;
|
||||
}
|
||||
|
||||
|
||||
enum FlightStatus {
|
||||
FLIGHT_STATUS_UNKNOWN = 0;
|
||||
FLIGHT_STATUS_FLYING = 1;
|
||||
FLIGHT_STATUS_DELAY = 2;
|
||||
FLIGHT_STATUS_CANCELED = 3;
|
||||
FLIGHT_STATUS_COMPLETED = 4;
|
||||
}
|
||||
|
||||
|
||||
enum SeatType {
|
||||
SEAT_TYPE_UNKNOWN = 0;
|
||||
SEAT_TYPE_WINDOW = 1;
|
||||
SEAT_TYPE_MIDDLE = 2;
|
||||
SEAT_TYPE_AISLE = 3;
|
||||
}
|
||||
|
||||
|
||||
enum SeatClass {
|
||||
SEAT_CLASS_UNKNOWN = 0;
|
||||
SEAT_CLASS_FIRST_CLASS = 1;
|
||||
SEAT_CLASS_BUSINESS = 2;
|
||||
SEAT_CLASS_ECONOMY = 3;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package passenger;
|
||||
|
||||
service PassengerGrpcService {
|
||||
|
||||
rpc GetById (GetByIdRequest) returns (GetPassengerByIdResult);
|
||||
}
|
||||
|
||||
message GetByIdRequest {
|
||||
string Id = 1;
|
||||
}
|
||||
|
||||
message GetPassengerByIdResult {
|
||||
PassengerResponse PassengerDto = 1;
|
||||
}
|
||||
|
||||
message PassengerResponse {
|
||||
string Id = 1;
|
||||
string Name = 2;
|
||||
string PassportNumber = 3;
|
||||
PassengerType PassengerType = 4;
|
||||
int32 Age = 5;
|
||||
string Email = 6;
|
||||
}
|
||||
|
||||
|
||||
enum PassengerType {
|
||||
PASSENGER_TYPE_UNKNOWN = 0;
|
||||
PASSENGER_TYPE_MALE = 1;
|
||||
PASSENGER_TYPE_FEMALE = 2;
|
||||
PASSENGER_TYPE_BABY = 3;
|
||||
}
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
namespace Flight.Aircrafts.Dtos;
|
||||
|
||||
public record AircraftDto(long Id, string Name, string Model, int ManufacturingYear);
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
|
||||
using System.Net;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class AircraftAlreadyExistException : AppException
|
||||
{
|
||||
public AircraftAlreadyExistException() : base("Aircraft already exist!", HttpStatusCode.Conflict)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidAircraftIdException : BadRequestException
|
||||
{
|
||||
public InvalidAircraftIdException(Guid aircraftId)
|
||||
: base($"AircraftId: '{aircraftId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidManufacturingYearException : BadRequestException
|
||||
{
|
||||
public InvalidManufacturingYearException() : base("ManufacturingYear must be greater than 1900")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidModelException : BadRequestException
|
||||
{
|
||||
public InvalidModelException() : base("Model cannot be empty or whitespace.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidNameException : BadRequestException
|
||||
{
|
||||
public InvalidNameException() : base("Name cannot be empty or whitespace.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
using Flight.Aircrafts.Models;
|
||||
using Mapster;
|
||||
|
||||
namespace Flight.Aircrafts.Features;
|
||||
|
||||
using CreatingAircraft.V1;
|
||||
using MassTransit;
|
||||
using ValueObjects;
|
||||
|
||||
public class AircraftMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<CreateAircraftMongo, AircraftReadModel>()
|
||||
.Map(d => d.Id, s => NewId.NextGuid())
|
||||
.Map(d => d.AircraftId, s => AircraftId.Of(s.Id));
|
||||
|
||||
config.NewConfig<Aircraft, AircraftReadModel>()
|
||||
.Map(d => d.Id, s => NewId.NextGuid())
|
||||
.Map(d => d.AircraftId, s => AircraftId.Of(s.Id.Value));
|
||||
|
||||
config.NewConfig<CreateAircraftRequestDto, CreatingAircraft.V1.CreateAircraft>()
|
||||
.ConstructUsing(x => new CreatingAircraft.V1.CreateAircraft(x.Name, x.Model, x.ManufacturingYear));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Web;
|
||||
using Data;
|
||||
using Duende.IdentityServer.EntityFramework.Entities;
|
||||
using Exceptions;
|
||||
using Flight.Aircrafts.ValueObjects;
|
||||
using FluentValidation;
|
||||
using Mapster;
|
||||
using MapsterMapper;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Models;
|
||||
|
||||
public record CreateAircraft(string Name, string Model, int ManufacturingYear) : ICommand<CreateAircraftResult>,
|
||||
IInternalCommand
|
||||
{
|
||||
public Guid Id { get; init; } = NewId.NextGuid();
|
||||
}
|
||||
|
||||
public record CreateAircraftResult(AircraftId Id);
|
||||
|
||||
public record AircraftCreatedDomainEvent
|
||||
(Guid Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent;
|
||||
|
||||
public record CreateAircraftRequestDto(string Name, string Model, int ManufacturingYear);
|
||||
|
||||
public record CreateAircraftResponseDto(Guid Id);
|
||||
|
||||
public class CreateAircraftEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", async (CreateAircraftRequestDto request,
|
||||
IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var command = mapper.Map<CreateAircraft>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
var response = result.Adapt<CreateAircraftResponseDto>();
|
||||
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.RequireAuthorization(nameof(ApiScope))
|
||||
.WithName("CreateAircraft")
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.Produces<CreateAircraftResponseDto>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.WithSummary("Create Aircraft")
|
||||
.WithDescription("Create Aircraft")
|
||||
.WithOpenApi()
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateAircraftValidator : AbstractValidator<CreateAircraft>
|
||||
{
|
||||
public CreateAircraftValidator()
|
||||
{
|
||||
RuleFor(x => x.Model).NotEmpty().WithMessage("Model is required");
|
||||
RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required");
|
||||
RuleFor(x => x.ManufacturingYear).NotEmpty().WithMessage("ManufacturingYear is required");
|
||||
}
|
||||
}
|
||||
|
||||
internal class CreateAircraftHandler : IRequestHandler<CreateAircraft, CreateAircraftResult>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
|
||||
public CreateAircraftHandler(FlightDbContext flightDbContext)
|
||||
{
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<CreateAircraftResult> Handle(CreateAircraft request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var aircraft = await _flightDbContext.Aircraft.SingleOrDefaultAsync(
|
||||
a => a.Model.Value == request.Model, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AircraftAlreadyExistException();
|
||||
}
|
||||
|
||||
var aircraftEntity = Aircraft.Create(AircraftId.Of(request.Id), Name.Of(request.Name), Model.Of(request.Model), ManufacturingYear.Of(request.ManufacturingYear));
|
||||
|
||||
var newAircraft = (await _flightDbContext.Aircraft.AddAsync(aircraftEntity, cancellationToken)).Entity;
|
||||
|
||||
return new CreateAircraftResult(newAircraft.Id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Exceptions;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
using ValueObjects;
|
||||
|
||||
public record CreateAircraftMongo(Guid Id, string Name, string Model, int ManufacturingYear, bool IsDeleted = false) : InternalCommand;
|
||||
|
||||
internal class CreateAircraftMongoHandler : ICommandHandler<CreateAircraftMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAircraftMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateAircraftMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var aircraftReadModel = _mapper.Map<AircraftReadModel>(request);
|
||||
|
||||
var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId &&
|
||||
!x.IsDeleted, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AircraftAlreadyExistException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using BuildingBlocks.Core.Model;
|
||||
|
||||
namespace Flight.Aircrafts.Models;
|
||||
|
||||
using Features.CreatingAircraft.V1;
|
||||
using ValueObjects;
|
||||
|
||||
public record Aircraft : Aggregate<AircraftId>
|
||||
{
|
||||
public Name Name { get; private set; } = default!;
|
||||
public Model Model { get; private set; } = default!;
|
||||
public ManufacturingYear ManufacturingYear { get; private set; } = default!;
|
||||
|
||||
public static Aircraft Create(AircraftId id, Name name, Model model, ManufacturingYear manufacturingYear, bool isDeleted = false)
|
||||
{
|
||||
var aircraft = new Aircraft
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Model = model,
|
||||
ManufacturingYear = manufacturingYear
|
||||
};
|
||||
|
||||
var @event = new AircraftCreatedDomainEvent(
|
||||
aircraft.Id,
|
||||
aircraft.Name,
|
||||
aircraft.Model,
|
||||
aircraft.ManufacturingYear,
|
||||
isDeleted);
|
||||
|
||||
aircraft.AddDomainEvent(@event);
|
||||
|
||||
return aircraft;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Flight.Aircrafts.Models;
|
||||
|
||||
using System;
|
||||
|
||||
public class AircraftReadModel
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required Guid AircraftId { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Model { get; init; }
|
||||
public required int ManufacturingYear { get; init; }
|
||||
public required bool IsDeleted { get; init; }
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
namespace Flight.Aircrafts.ValueObjects;
|
||||
|
||||
using System;
|
||||
using Flight.Aircrafts.Exceptions;
|
||||
|
||||
public record AircraftId
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private AircraftId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static AircraftId Of(Guid value)
|
||||
{
|
||||
if (value == Guid.Empty)
|
||||
{
|
||||
throw new InvalidAircraftIdException(value);
|
||||
}
|
||||
|
||||
return new AircraftId(value);
|
||||
}
|
||||
|
||||
public static implicit operator Guid(AircraftId aircraftId)
|
||||
{
|
||||
return aircraftId.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Aircrafts.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record ManufacturingYear
|
||||
{
|
||||
public int Value { get; }
|
||||
|
||||
private ManufacturingYear(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static ManufacturingYear Of(int value)
|
||||
{
|
||||
if (value < 1900)
|
||||
{
|
||||
throw new InvalidManufacturingYearException();
|
||||
}
|
||||
|
||||
return new ManufacturingYear(value);
|
||||
}
|
||||
|
||||
public static implicit operator int(ManufacturingYear manufacturingYear)
|
||||
{
|
||||
return manufacturingYear.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Aircrafts.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record Model
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private Model(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Model Of(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidModelException();
|
||||
}
|
||||
|
||||
return new Model(value);
|
||||
}
|
||||
|
||||
public static implicit operator string(Model model)
|
||||
{
|
||||
return model.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Aircrafts.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record Name
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private Name(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Name Of(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidNameException();
|
||||
}
|
||||
|
||||
return new Name(value);
|
||||
}
|
||||
|
||||
public static implicit operator string(Name name)
|
||||
{
|
||||
return name.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
namespace Flight.Airports.Dtos;
|
||||
|
||||
public record AirportDto(long Id, string Name, string Address, string Code);
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Airports.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class AirportAlreadyExistException : ConflictException
|
||||
{
|
||||
public AirportAlreadyExistException(int? code = default) : base("Airport already exist!", code)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Flight.Airports.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidAddressException : BadRequestException
|
||||
{
|
||||
public InvalidAddressException() : base("Address cannot be empty or whitespace.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Airports.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidAirportIdException : BadRequestException
|
||||
{
|
||||
public InvalidAirportIdException(Guid airportId)
|
||||
: base($"airportId: '{airportId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Airports.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidCodeException : BadRequestException
|
||||
{
|
||||
public InvalidCodeException() : base("Code cannot be empty or whitespace.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Airports.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidNameException : BadRequestException
|
||||
{
|
||||
public InvalidNameException() : base("Name cannot be empty or whitespace.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
namespace Flight.Airports.Features;
|
||||
|
||||
using CreatingAirport.V1;
|
||||
using Mapster;
|
||||
using MassTransit;
|
||||
using Models;
|
||||
|
||||
public class AirportMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<CreateAirportMongo, AirportReadModel>()
|
||||
.Map(d => d.Id, s => NewId.NextGuid())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<Airport, AirportReadModel>()
|
||||
.Map(d => d.Id, s => NewId.NextGuid())
|
||||
.Map(d => d.AirportId, s => s.Id.Value);
|
||||
|
||||
config.NewConfig<CreateAirportRequestDto, CreateAirport>()
|
||||
.ConstructUsing(x => new CreateAirport(x.Name, x.Address, x.Code));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Web;
|
||||
using Data;
|
||||
using Duende.IdentityServer.EntityFramework.Entities;
|
||||
using Exceptions;
|
||||
using FluentValidation;
|
||||
using Mapster;
|
||||
using MapsterMapper;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ValueObjects;
|
||||
|
||||
public record CreateAirport(string Name, string Address, string Code) : ICommand<CreateAirportResult>, IInternalCommand
|
||||
{
|
||||
public Guid Id { get; init; } = NewId.NextGuid();
|
||||
}
|
||||
|
||||
public record CreateAirportResult(Guid Id);
|
||||
|
||||
public record AirportCreatedDomainEvent
|
||||
(Guid Id, string Name, string Address, string Code, bool IsDeleted) : IDomainEvent;
|
||||
|
||||
public record CreateAirportRequestDto(string Name, string Address, string Code);
|
||||
|
||||
public record CreateAirportResponseDto(Guid Id);
|
||||
|
||||
public class CreateAirportEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", async (CreateAirportRequestDto request,
|
||||
IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var command = mapper.Map<CreateAirport>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
var response = result.Adapt<CreateAirportResponseDto>();
|
||||
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.RequireAuthorization(nameof(ApiScope))
|
||||
.WithName("CreateAirport")
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.Produces<CreateAirportResponseDto>()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.WithSummary("Create Airport")
|
||||
.WithDescription("Create Airport")
|
||||
.WithOpenApi()
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateAirportValidator : AbstractValidator<CreateAirport>
|
||||
{
|
||||
public CreateAirportValidator()
|
||||
{
|
||||
RuleFor(x => x.Code).NotEmpty().WithMessage("Code is required");
|
||||
RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required");
|
||||
RuleFor(x => x.Address).NotEmpty().WithMessage("Address is required");
|
||||
}
|
||||
}
|
||||
|
||||
internal class CreateAirportHandler : IRequestHandler<CreateAirport, CreateAirportResult>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
|
||||
public CreateAirportHandler(FlightDbContext flightDbContext)
|
||||
{
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<CreateAirportResult> Handle(CreateAirport request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var airport =
|
||||
await _flightDbContext.Airports.SingleOrDefaultAsync(x => x.Code.Value == request.Code, cancellationToken);
|
||||
|
||||
if (airport is not null)
|
||||
{
|
||||
throw new AirportAlreadyExistException();
|
||||
}
|
||||
|
||||
var airportEntity = Models.Airport.Create(AirportId.Of(request.Id), Name.Of(request.Name), Address.Of(request.Address), Code.Of(request.Code));
|
||||
|
||||
var newAirport = (await _flightDbContext.Airports.AddAsync(airportEntity, cancellationToken)).Entity;
|
||||
|
||||
return new CreateAirportResult(newAirport.Id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Exceptions;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
public record CreateAirportMongo(Guid Id, string Name, string Address, string Code, bool IsDeleted = false) : InternalCommand;
|
||||
|
||||
internal class CreateAirportMongoHandler : ICommandHandler<CreateAirportMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAirportMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateAirportMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var airportReadModel = _mapper.Map<AirportReadModel>(request);
|
||||
|
||||
var aircraft = await _flightReadDbContext.Airport.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.AirportId == airportReadModel.AirportId &&
|
||||
!x.IsDeleted, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AirportAlreadyExistException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using BuildingBlocks.Core.Model;
|
||||
|
||||
namespace Flight.Airports.Models;
|
||||
|
||||
using Features.CreatingAirport.V1;
|
||||
using ValueObjects;
|
||||
|
||||
public record Airport : Aggregate<AirportId>
|
||||
{
|
||||
public Name Name { get; private set; } = default!;
|
||||
public Address Address { get; private set; } = default!;
|
||||
public Code Code { get; private set; } = default!;
|
||||
|
||||
public static Airport Create(AirportId id, Name name, Address address, Code code, bool isDeleted = false)
|
||||
{
|
||||
var airport = new Airport
|
||||
{
|
||||
Id = id,
|
||||
Name = name,
|
||||
Address = address,
|
||||
Code = code
|
||||
};
|
||||
|
||||
var @event = new AirportCreatedDomainEvent(
|
||||
airport.Id,
|
||||
airport.Name,
|
||||
airport.Address,
|
||||
airport.Code,
|
||||
isDeleted);
|
||||
|
||||
airport.AddDomainEvent(@event);
|
||||
|
||||
return airport;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Flight.Airports.Models;
|
||||
|
||||
using System;
|
||||
|
||||
public class AirportReadModel
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required Guid AirportId { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public string Address { get; init; }
|
||||
public required string Code { get; init; }
|
||||
public required bool IsDeleted { get; init; }
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Airports.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public class Address
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private Address(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Address Of(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidAddressException();
|
||||
}
|
||||
|
||||
return new Address(value);
|
||||
}
|
||||
|
||||
public static implicit operator string(Address address)
|
||||
{
|
||||
return address.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
namespace Flight.Airports.ValueObjects;
|
||||
|
||||
using System;
|
||||
using Flight.Airports.Exceptions;
|
||||
|
||||
public record AirportId
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private AirportId(Guid value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static AirportId Of(Guid value)
|
||||
{
|
||||
if (value == Guid.Empty)
|
||||
{
|
||||
throw new InvalidAirportIdException(value);
|
||||
}
|
||||
|
||||
return new AirportId(value);
|
||||
}
|
||||
|
||||
public static implicit operator Guid(AirportId airportId)
|
||||
{
|
||||
return airportId.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Airports.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record Code
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private Code(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Code Of(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidCodeException();
|
||||
}
|
||||
|
||||
return new Code(value);
|
||||
}
|
||||
|
||||
public static implicit operator string(Code code)
|
||||
{
|
||||
return code.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Flight.Airports.ValueObjects;
|
||||
|
||||
using Exceptions;
|
||||
|
||||
public record Name
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private Name(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public static Name Of(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new InvalidNameException();
|
||||
}
|
||||
|
||||
return new Name(value);
|
||||
}
|
||||
|
||||
public static implicit operator string(Name name)
|
||||
{
|
||||
return name.Value;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
using Flight.Aircrafts.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Flight.Data.Configurations;
|
||||
|
||||
using System;
|
||||
using Aircrafts.ValueObjects;
|
||||
|
||||
public class AircraftConfiguration : IEntityTypeConfiguration<Aircraft>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Aircraft> builder)
|
||||
{
|
||||
|
||||
builder.ToTable(nameof(Aircraft));
|
||||
|
||||
builder.HasKey(r => r.Id);
|
||||
builder.Property(r => r.Id).ValueGeneratedNever()
|
||||
.HasConversion<Guid>(aircraftId => aircraftId.Value, dbId => AircraftId.Of(dbId));
|
||||
|
||||
builder.Property(r => r.Version).IsConcurrencyToken();
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Name,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Aircraft.Name))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Model,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Aircraft.Model))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.ManufacturingYear,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Aircraft.ManufacturingYear))
|
||||
.HasMaxLength(5)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using Flight.Airports.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Flight.Data.Configurations;
|
||||
|
||||
using Airports.ValueObjects;
|
||||
|
||||
public class AirportConfiguration : IEntityTypeConfiguration<Airport>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Airport> builder)
|
||||
{
|
||||
|
||||
builder.ToTable(nameof(Airport));
|
||||
|
||||
builder.HasKey(r => r.Id);
|
||||
builder.Property(r => r.Id).ValueGeneratedNever()
|
||||
.HasConversion<Guid>(airportId => airportId.Value, dbId => AirportId.Of(dbId));
|
||||
// // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api
|
||||
builder.Property(r => r.Version).IsConcurrencyToken();
|
||||
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Name,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Airport.Name))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Address,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Airport.Address))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Code,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Airport.Code))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Airports.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Flight.Data.Configurations;
|
||||
|
||||
using System;
|
||||
using Flights.Models;
|
||||
using Flights.ValueObjects;
|
||||
|
||||
public class FlightConfiguration : IEntityTypeConfiguration<Flights.Models.Flight>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Flights.Models.Flight> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Flight));
|
||||
|
||||
builder.HasKey(r => r.Id);
|
||||
builder.Property(r => r.Id).ValueGeneratedNever()
|
||||
.HasConversion<Guid>(flight => flight.Value, dbId => FlightId.Of(dbId));
|
||||
|
||||
builder.Property(r => r.Version).IsConcurrencyToken();
|
||||
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.FlightNumber,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.FlightNumber))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder
|
||||
.HasOne<Aircraft>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.AircraftId)
|
||||
.IsRequired();
|
||||
|
||||
builder
|
||||
.HasOne<Airport>()
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.DepartureAirportId)
|
||||
.IsRequired();
|
||||
|
||||
builder
|
||||
.HasOne<Airport>()
|
||||
.WithMany()
|
||||
.HasForeignKey(d => d.ArriveAirportId)
|
||||
.IsRequired();
|
||||
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.DurationMinutes,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.DurationMinutes))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.HasDefaultValue(Flights.Enums.FlightStatus.Unknown)
|
||||
.HasConversion(
|
||||
x => x.ToString(),
|
||||
x => (Flights.Enums.FlightStatus)Enum.Parse(typeof(Flights.Enums.FlightStatus), x));
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.Price,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.Price))
|
||||
.HasMaxLength(10)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.ArriveDate,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.ArriveDate))
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.DepartureDate,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.DepartureDate))
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.FlightDate,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Flight.FlightDate))
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
using Flight.Seats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace Flight.Data.Configurations;
|
||||
|
||||
using System;
|
||||
using Seats.ValueObjects;
|
||||
|
||||
public class SeatConfiguration : IEntityTypeConfiguration<Seat>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Seat> builder)
|
||||
{
|
||||
builder.ToTable(nameof(Seat));
|
||||
|
||||
builder.HasKey(r => r.Id);
|
||||
builder.Property(r => r.Id).ValueGeneratedNever()
|
||||
.HasConversion<Guid>(seatId => seatId.Value, dbId => SeatId.Of(dbId));
|
||||
|
||||
builder.Property(r => r.Version).IsConcurrencyToken();
|
||||
|
||||
builder.OwnsOne(
|
||||
x => x.SeatNumber,
|
||||
a =>
|
||||
{
|
||||
a.Property(p => p.Value)
|
||||
.HasColumnName(nameof(Seat.SeatNumber))
|
||||
.HasMaxLength(50)
|
||||
.IsRequired();
|
||||
}
|
||||
);
|
||||
|
||||
builder
|
||||
.HasOne<Flights.Models.Flight>()
|
||||
.WithMany()
|
||||
.HasForeignKey(p => p.FlightId);
|
||||
|
||||
builder.Property(x => x.Class)
|
||||
.HasDefaultValue(Seats.Enums.SeatClass.Unknown)
|
||||
.HasConversion(
|
||||
x => x.ToString(),
|
||||
x => (Flight.Seats.Enums.SeatClass)Enum.Parse(typeof(Flight.Seats.Enums.SeatClass), x));
|
||||
|
||||
builder.Property(x => x.Type)
|
||||
.HasDefaultValue(Seats.Enums.SeatType.Unknown)
|
||||
.HasConversion(
|
||||
x => x.ToString(),
|
||||
x => (Flight.Seats.Enums.SeatType)Enum.Parse(typeof(Flight.Seats.Enums.SeatType), x));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace Flight.Data
|
||||
{
|
||||
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<FlightDbContext>
|
||||
{
|
||||
public FlightDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var builder = new DbContextOptionsBuilder<FlightDbContext>();
|
||||
|
||||
builder.UseNpgsql("Server=localhost;Port=5432;Database=flight;User Id=postgres;Password=postgres;Include Error Detail=true")
|
||||
.UseSnakeCaseNamingConvention();
|
||||
return new FlightDbContext(builder.Options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
using System.Text.Json;
|
||||
using System.Transactions;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.PersistMessageProcessor;
|
||||
using BuildingBlocks.Polly;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Flight.Data;
|
||||
|
||||
public class EfTxFlightBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
|
||||
where TRequest : notnull, IRequest<TResponse>
|
||||
where TResponse : notnull
|
||||
{
|
||||
private readonly ILogger<EfTxFlightBehavior<TRequest, TResponse>> _logger;
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IPersistMessageDbContext _persistMessageDbContext;
|
||||
private readonly IEventDispatcher _eventDispatcher;
|
||||
|
||||
public EfTxFlightBehavior(
|
||||
ILogger<EfTxFlightBehavior<TRequest, TResponse>> logger,
|
||||
FlightDbContext flightDbContext,
|
||||
IPersistMessageDbContext persistMessageDbContext,
|
||||
IEventDispatcher eventDispatcher
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_flightDbContext = flightDbContext;
|
||||
_persistMessageDbContext = persistMessageDbContext;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
}
|
||||
|
||||
public async Task<TResponse> Handle(
|
||||
TRequest request,
|
||||
RequestHandlerDelegate<TResponse> next,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"{Prefix} Handled command {MediatrRequest}",
|
||||
GetType().Name,
|
||||
typeof(TRequest).FullName);
|
||||
|
||||
_logger.LogDebug(
|
||||
"{Prefix} Handled command {MediatrRequest} with content {RequestContent}",
|
||||
GetType().Name,
|
||||
typeof(TRequest).FullName,
|
||||
JsonSerializer.Serialize(request));
|
||||
|
||||
var response = await next();
|
||||
|
||||
_logger.LogInformation(
|
||||
"{Prefix} Executed the {MediatrRequest} request",
|
||||
GetType().Name,
|
||||
typeof(TRequest).FullName);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var domainEvents = _flightDbContext.GetDomainEvents();
|
||||
|
||||
if (domainEvents is null || !domainEvents.Any())
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"{Prefix} Open the transaction for {MediatrRequest}",
|
||||
GetType().Name,
|
||||
typeof(TRequest).FullName);
|
||||
|
||||
using var scope = new TransactionScope(
|
||||
TransactionScopeOption.Required,
|
||||
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
|
||||
TransactionScopeAsyncFlowOption.Enabled);
|
||||
|
||||
await _eventDispatcher.SendAsync(
|
||||
domainEvents.ToArray(),
|
||||
typeof(TRequest),
|
||||
cancellationToken);
|
||||
|
||||
// Save data to database with some retry policy in distributed transaction
|
||||
await _flightDbContext.RetryOnFailure(
|
||||
async () =>
|
||||
{
|
||||
await _flightDbContext.SaveChangesAsync(cancellationToken);
|
||||
});
|
||||
|
||||
// Save data to database with some retry policy in distributed transaction
|
||||
await _persistMessageDbContext.RetryOnFailure(
|
||||
async () =>
|
||||
{
|
||||
await _persistMessageDbContext.SaveChangesAsync(cancellationToken);
|
||||
});
|
||||
|
||||
scope.Complete();
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
using BuildingBlocks.EFCore;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Airports.Models;
|
||||
using Flight.Seats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Data;
|
||||
|
||||
using BuildingBlocks.Web;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
public sealed class FlightDbContext : AppDbContextBase
|
||||
{
|
||||
public FlightDbContext(DbContextOptions<FlightDbContext> options, ICurrentUserProvider? currentUserProvider = null,
|
||||
ILogger<FlightDbContext>? logger = null) : base(
|
||||
options, currentUserProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<Flights.Models.Flight> Flights => Set<Flights.Models.Flight>();
|
||||
public DbSet<Airport> Airports => Set<Airport>();
|
||||
public DbSet<Aircraft> Aircraft => Set<Aircraft>();
|
||||
public DbSet<Seat> Seats => Set<Seat>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
builder.ApplyConfigurationsFromAssembly(typeof(FlightRoot).Assembly);
|
||||
builder.FilterSoftDeletedProperties();
|
||||
builder.ToSnakeCaseTables();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using BuildingBlocks.Mongo;
|
||||
using Humanizer;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Flight.Data;
|
||||
|
||||
using Aircrafts.Models;
|
||||
using Airports.Models;
|
||||
using Flights.Models;
|
||||
using Seats.Models;
|
||||
|
||||
public class FlightReadDbContext : MongoDbContext
|
||||
{
|
||||
public FlightReadDbContext(IOptions<MongoOptions> options) : base(options)
|
||||
{
|
||||
Flight = GetCollection<FlightReadModel>(nameof(Flight).Underscore());
|
||||
Aircraft = GetCollection<AircraftReadModel>(nameof(Aircraft).Underscore());
|
||||
Airport = GetCollection<AirportReadModel>(nameof(Airport).Underscore());
|
||||
Seat = GetCollection<SeatReadModel>(nameof(Seat).Underscore());
|
||||
}
|
||||
|
||||
public IMongoCollection<FlightReadModel> Flight { get; }
|
||||
public IMongoCollection<AircraftReadModel> Aircraft { get; }
|
||||
public IMongoCollection<AirportReadModel> Airport { get; }
|
||||
public IMongoCollection<SeatReadModel> Seat { get; }
|
||||
}
|
||||
@ -0,0 +1,583 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Flight.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Flight.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(FlightDbContext))]
|
||||
[Migration("20230611230948_initial")]
|
||||
partial class initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b.ToTable("aircraft", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Airports.Models.Airport", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b.ToTable("airport", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("aircraft_id");
|
||||
|
||||
b.Property<Guid>("ArriveAirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("arrive_airport_id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<Guid>("DepartureAirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("departure_airport_id");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b.HasIndex("AircraftId")
|
||||
.HasDatabaseName("ix_flight_aircraft_id");
|
||||
|
||||
b.HasIndex("ArriveAirportId")
|
||||
.HasDatabaseName("ix_flight_arrive_airport_id");
|
||||
|
||||
b.HasIndex("DepartureAirportId")
|
||||
.HasDatabaseName("ix_flight_departure_airport_id");
|
||||
|
||||
b.ToTable("flight", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Class")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("class");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("flight_id");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_seat");
|
||||
|
||||
b.HasIndex("FlightId")
|
||||
.HasDatabaseName("ix_seat_flight_id");
|
||||
|
||||
b.ToTable("seat", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
|
||||
{
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.ManufacturingYear", "ManufacturingYear", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<int>("Value")
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("manufacturing_year");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.Model", "Model", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.Name", "Name", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.Navigation("ManufacturingYear")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Model")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Name")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Airports.Models.Airport", b =>
|
||||
{
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Address", "Address", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("address");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Code", "Code", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("code");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Name", "Name", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.Navigation("Address")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Code")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Name")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
|
||||
{
|
||||
b.HasOne("Flight.Aircrafts.Models.Aircraft", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AircraftId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_aircraft_aircraft_id");
|
||||
|
||||
b.HasOne("Flight.Airports.Models.Airport", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ArriveAirportId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_airport_arrive_airport_id");
|
||||
|
||||
b.HasOne("Flight.Airports.Models.Airport", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DepartureAirportId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_airport_departure_airport_id");
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.ArriveDate", "ArriveDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("arrive_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.DepartureDate", "DepartureDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("departure_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.DurationMinutes", "DurationMinutes", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<decimal>("Value")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("duration_minutes");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.FlightDate", "FlightDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("flight_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.FlightNumber", "FlightNumber", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("flight_number");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.Price", "Price", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<decimal>("Value")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("price");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.Navigation("ArriveDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DepartureDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DurationMinutes")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("FlightDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("FlightNumber")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Price")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
|
||||
{
|
||||
b.HasOne("Flight.Flights.Models.Flight", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("FlightId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_seat_flight_flight_id");
|
||||
|
||||
b.OwnsOne("Flight.Seats.ValueObjects.SeatNumber", "SeatNumber", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("SeatId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("seat_number");
|
||||
|
||||
b1.HasKey("SeatId")
|
||||
.HasName("pk_seat");
|
||||
|
||||
b1.ToTable("seat");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("SeatId")
|
||||
.HasConstraintName("fk_seat_seat_id");
|
||||
});
|
||||
|
||||
b.Navigation("SeatNumber")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Flight.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "aircraft",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
model = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
manufacturingyear = table.Column<int>(name: "manufacturing_year", type: "integer", maxLength: 5, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created_at", type: "timestamp with time zone", nullable: true),
|
||||
createdby = table.Column<long>(name: "created_by", type: "bigint", nullable: true),
|
||||
lastmodified = table.Column<DateTime>(name: "last_modified", type: "timestamp with time zone", nullable: true),
|
||||
lastmodifiedby = table.Column<long>(name: "last_modified_by", type: "bigint", nullable: true),
|
||||
isdeleted = table.Column<bool>(name: "is_deleted", type: "boolean", nullable: false),
|
||||
version = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_aircraft", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "airport",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
address = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
code = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created_at", type: "timestamp with time zone", nullable: true),
|
||||
createdby = table.Column<long>(name: "created_by", type: "bigint", nullable: true),
|
||||
lastmodified = table.Column<DateTime>(name: "last_modified", type: "timestamp with time zone", nullable: true),
|
||||
lastmodifiedby = table.Column<long>(name: "last_modified_by", type: "bigint", nullable: true),
|
||||
isdeleted = table.Column<bool>(name: "is_deleted", type: "boolean", nullable: false),
|
||||
version = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_airport", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "flight",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
flightnumber = table.Column<string>(name: "flight_number", type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
aircraftid = table.Column<Guid>(name: "aircraft_id", type: "uuid", nullable: false),
|
||||
departureairportid = table.Column<Guid>(name: "departure_airport_id", type: "uuid", nullable: false),
|
||||
arriveairportid = table.Column<Guid>(name: "arrive_airport_id", type: "uuid", nullable: false),
|
||||
durationminutes = table.Column<decimal>(name: "duration_minutes", type: "numeric", maxLength: 50, nullable: false),
|
||||
status = table.Column<string>(type: "text", nullable: false, defaultValue: "Unknown"),
|
||||
price = table.Column<decimal>(type: "numeric", maxLength: 10, nullable: false),
|
||||
arrivedate = table.Column<DateTime>(name: "arrive_date", type: "timestamp with time zone", nullable: false),
|
||||
departuredate = table.Column<DateTime>(name: "departure_date", type: "timestamp with time zone", nullable: false),
|
||||
flightdate = table.Column<DateTime>(name: "flight_date", type: "timestamp with time zone", nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created_at", type: "timestamp with time zone", nullable: true),
|
||||
createdby = table.Column<long>(name: "created_by", type: "bigint", nullable: true),
|
||||
lastmodified = table.Column<DateTime>(name: "last_modified", type: "timestamp with time zone", nullable: true),
|
||||
lastmodifiedby = table.Column<long>(name: "last_modified_by", type: "bigint", nullable: true),
|
||||
isdeleted = table.Column<bool>(name: "is_deleted", type: "boolean", nullable: false),
|
||||
version = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_flight", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_flight_aircraft_aircraft_id",
|
||||
column: x => x.aircraftid,
|
||||
principalTable: "aircraft",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_flight_airport_arrive_airport_id",
|
||||
column: x => x.arriveairportid,
|
||||
principalTable: "airport",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_flight_airport_departure_airport_id",
|
||||
column: x => x.departureairportid,
|
||||
principalTable: "airport",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "seat",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
seatnumber = table.Column<string>(name: "seat_number", type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
type = table.Column<string>(type: "text", nullable: false, defaultValue: "Unknown"),
|
||||
@class = table.Column<string>(name: "class", type: "text", nullable: false, defaultValue: "Unknown"),
|
||||
flightid = table.Column<Guid>(name: "flight_id", type: "uuid", nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created_at", type: "timestamp with time zone", nullable: true),
|
||||
createdby = table.Column<long>(name: "created_by", type: "bigint", nullable: true),
|
||||
lastmodified = table.Column<DateTime>(name: "last_modified", type: "timestamp with time zone", nullable: true),
|
||||
lastmodifiedby = table.Column<long>(name: "last_modified_by", type: "bigint", nullable: true),
|
||||
isdeleted = table.Column<bool>(name: "is_deleted", type: "boolean", nullable: false),
|
||||
version = table.Column<long>(type: "bigint", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_seat", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_seat_flight_flight_id",
|
||||
column: x => x.flightid,
|
||||
principalTable: "flight",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_flight_aircraft_id",
|
||||
table: "flight",
|
||||
column: "aircraft_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_flight_arrive_airport_id",
|
||||
table: "flight",
|
||||
column: "arrive_airport_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_flight_departure_airport_id",
|
||||
table: "flight",
|
||||
column: "departure_airport_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_seat_flight_id",
|
||||
table: "seat",
|
||||
column: "flight_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "seat");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "flight");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "aircraft");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "airport");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,580 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Flight.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Flight.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(FlightDbContext))]
|
||||
partial class FlightDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b.ToTable("aircraft", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Airports.Models.Airport", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b.ToTable("airport", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("aircraft_id");
|
||||
|
||||
b.Property<Guid>("ArriveAirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("arrive_airport_id");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<Guid>("DepartureAirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("departure_airport_id");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b.HasIndex("AircraftId")
|
||||
.HasDatabaseName("ix_flight_aircraft_id");
|
||||
|
||||
b.HasIndex("ArriveAirportId")
|
||||
.HasDatabaseName("ix_flight_arrive_airport_id");
|
||||
|
||||
b.HasIndex("DepartureAirportId")
|
||||
.HasDatabaseName("ix_flight_departure_airport_id");
|
||||
|
||||
b.ToTable("flight", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Class")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("class");
|
||||
|
||||
b.Property<DateTime?>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<long?>("CreatedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("created_by");
|
||||
|
||||
b.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("flight_id");
|
||||
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
b.Property<DateTime?>("LastModified")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_modified");
|
||||
|
||||
b.Property<long?>("LastModifiedBy")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("last_modified_by");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("Unknown")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<long>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("version");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_seat");
|
||||
|
||||
b.HasIndex("FlightId")
|
||||
.HasDatabaseName("ix_seat_flight_id");
|
||||
|
||||
b.ToTable("seat", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b =>
|
||||
{
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.ManufacturingYear", "ManufacturingYear", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<int>("Value")
|
||||
.HasMaxLength(5)
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("manufacturing_year");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.Model", "Model", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("model");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Aircrafts.ValueObjects.Name", "Name", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AircraftId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b1.HasKey("AircraftId")
|
||||
.HasName("pk_aircraft");
|
||||
|
||||
b1.ToTable("aircraft");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AircraftId")
|
||||
.HasConstraintName("fk_aircraft_aircraft_id");
|
||||
});
|
||||
|
||||
b.Navigation("ManufacturingYear")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Model")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Name")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Airports.Models.Airport", b =>
|
||||
{
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Address", "Address", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("address");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Code", "Code", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("code");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Airports.ValueObjects.Name", "Name", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("AirportId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b1.HasKey("AirportId")
|
||||
.HasName("pk_airport");
|
||||
|
||||
b1.ToTable("airport");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("AirportId")
|
||||
.HasConstraintName("fk_airport_airport_id");
|
||||
});
|
||||
|
||||
b.Navigation("Address")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Code")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Name")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Flights.Models.Flight", b =>
|
||||
{
|
||||
b.HasOne("Flight.Aircrafts.Models.Aircraft", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("AircraftId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_aircraft_aircraft_id");
|
||||
|
||||
b.HasOne("Flight.Airports.Models.Airport", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("ArriveAirportId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_airport_arrive_airport_id");
|
||||
|
||||
b.HasOne("Flight.Airports.Models.Airport", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("DepartureAirportId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_flight_airport_departure_airport_id");
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.ArriveDate", "ArriveDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("arrive_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.DepartureDate", "DepartureDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("departure_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.DurationMinutes", "DurationMinutes", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<decimal>("Value")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("duration_minutes");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.FlightDate", "FlightDate", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<DateTime>("Value")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("flight_date");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.FlightNumber", "FlightNumber", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("flight_number");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.OwnsOne("Flight.Flights.ValueObjects.Price", "Price", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("FlightId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<decimal>("Value")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("numeric")
|
||||
.HasColumnName("price");
|
||||
|
||||
b1.HasKey("FlightId")
|
||||
.HasName("pk_flight");
|
||||
|
||||
b1.ToTable("flight");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("FlightId")
|
||||
.HasConstraintName("fk_flight_flight_id");
|
||||
});
|
||||
|
||||
b.Navigation("ArriveDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DepartureDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("DurationMinutes")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("FlightDate")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("FlightNumber")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Price")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Flight.Seats.Models.Seat", b =>
|
||||
{
|
||||
b.HasOne("Flight.Flights.Models.Flight", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("FlightId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_seat_flight_flight_id");
|
||||
|
||||
b.OwnsOne("Flight.Seats.ValueObjects.SeatNumber", "SeatNumber", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("SeatId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b1.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("seat_number");
|
||||
|
||||
b1.HasKey("SeatId")
|
||||
.HasName("pk_seat");
|
||||
|
||||
b1.ToTable("seat");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("SeatId")
|
||||
.HasConstraintName("fk_seat_seat_id");
|
||||
});
|
||||
|
||||
b.Navigation("SeatNumber")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
using BuildingBlocks.EFCore;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Airports.Models;
|
||||
using Flight.Flights.Models;
|
||||
using Flight.Seats.Models;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Data.Seed;
|
||||
|
||||
public class FlightDataSeeder(
|
||||
FlightDbContext flightDbContext,
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper
|
||||
) : IDataSeeder
|
||||
{
|
||||
public async Task SeedAllAsync()
|
||||
{
|
||||
var pendingMigrations = await flightDbContext.Database.GetPendingMigrationsAsync();
|
||||
|
||||
if (!pendingMigrations.Any())
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
namespace Flight.Data.Seed;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Aircrafts.Models;
|
||||
using Airports.Models;
|
||||
using Airports.ValueObjects;
|
||||
using Flight.Aircrafts.ValueObjects;
|
||||
using Flights.Models;
|
||||
using Flights.ValueObjects;
|
||||
using MassTransit;
|
||||
using Seats.Models;
|
||||
using Seats.ValueObjects;
|
||||
using AirportName = Airports.ValueObjects.Name;
|
||||
using Name = Aircrafts.ValueObjects.Name;
|
||||
|
||||
public static class InitialData
|
||||
{
|
||||
public static List<Airport> Airports { get; }
|
||||
public static List<Aircraft> Aircrafts { get; }
|
||||
public static List<Seat> Seats { get; }
|
||||
public static List<Flight> Flights { get; }
|
||||
|
||||
|
||||
static InitialData()
|
||||
{
|
||||
Airports = new List<Airport>
|
||||
{
|
||||
Airport.Create(AirportId.Of(new Guid("3c5c0000-97c6-fc34-a0cb-08db322230c8")), AirportName.Of("Lisbon International Airport"), Address.Of("LIS"), Code.Of("12988")),
|
||||
Airport.Create(AirportId.Of(new Guid("3c5c0000-97c6-fc34-fc3c-08db322230c8")), AirportName.Of("Sao Paulo International Airport"), Address.Of("BRZ"), Code.Of("11200"))
|
||||
};
|
||||
|
||||
Aircrafts = new List<Aircraft>
|
||||
{
|
||||
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-fcd3-08db322230c8")), Name.Of("Boeing 737"), Model.Of("B737"), ManufacturingYear.Of(2005)),
|
||||
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e04-08db322230c9")), Name.Of("Airbus 300"), Model.Of("A300"), ManufacturingYear.Of(2000)),
|
||||
Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e11-08db322230c9")), Name.Of("Airbus 320"), Model.Of("A320"), ManufacturingYear.Of(2003))
|
||||
};
|
||||
|
||||
|
||||
Flights = new List<Flight>
|
||||
{
|
||||
Flight.Create(FlightId.Of(new Guid("3c5c0000-97c6-fc34-2eb9-08db322230c9")), FlightNumber.Of("BD467"), AircraftId.Of(Aircrafts.First().Id.Value), AirportId.Of( Airports.First().Id), DepartureDate.Of(new DateTime(2022, 1, 31, 12, 0, 0)),
|
||||
ArriveDate.Of(new DateTime(2022, 1, 31, 14, 0, 0)),
|
||||
AirportId.Of(Airports.Last().Id), DurationMinutes.Of(120m),
|
||||
FlightDate.Of(new DateTime(2022, 1, 31, 13, 0, 0)), global::Flight.Flights.Enums.FlightStatus.Completed,
|
||||
Price.Of(8000))
|
||||
};
|
||||
|
||||
Seats = new List<Seat>
|
||||
{
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of( "12A"), global::Flight.Seats.Enums.SeatType.Window, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid)Flights.First().Id)),
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of("12B"), global::Flight.Seats.Enums.SeatType.Window, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid)Flights.First().Id)),
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of("12C"), global::Flight.Seats.Enums.SeatType.Middle, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid) Flights.First().Id)),
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of("12D"), global::Flight.Seats.Enums.SeatType.Middle, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid) Flights.First().Id)),
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of("12E"), global::Flight.Seats.Enums.SeatType.Aisle, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid) Flights.First().Id)),
|
||||
Seat.Create(SeatId.Of(NewId.NextGuid()), SeatNumber.Of("12F"), global::Flight.Seats.Enums.SeatType.Aisle, global::Flight.Seats.Enums.SeatClass.Economy, FlightId.Of((Guid) Flights.First().Id))
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
dotnet ef migrations add initial --context FlightDbContext -o "Data\Migrations"
|
||||
dotnet ef database update --context FlightDbContext
|
||||
@ -0,0 +1,51 @@
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight;
|
||||
|
||||
using Aircrafts.Features.CreatingAircraft.V1;
|
||||
using Aircrafts.ValueObjects;
|
||||
using Airports.Features.CreatingAirport.V1;
|
||||
using Flights.Features.CreatingFlight.V1;
|
||||
using Flights.Features.DeletingFlight.V1;
|
||||
using Flights.Features.UpdatingFlight.V1;
|
||||
using Seats.Features.CreatingSeat.V1;
|
||||
using Seats.Features.ReservingSeat.V1;
|
||||
|
||||
// ref: https://www.ledjonbehluli.com/posts/domain_to_integration_event/
|
||||
public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
public IIntegrationEvent? MapToIntegrationEvent(IDomainEvent @event)
|
||||
{
|
||||
return @event switch
|
||||
{
|
||||
FlightCreatedDomainEvent e => new FlightCreated(e.Id),
|
||||
FlightUpdatedDomainEvent e => new FlightUpdated(e.Id),
|
||||
FlightDeletedDomainEvent e => new FlightDeleted(e.Id),
|
||||
AirportCreatedDomainEvent e => new AirportCreated(e.Id),
|
||||
AircraftCreatedDomainEvent e => new AircraftCreated(e.Id),
|
||||
SeatCreatedDomainEvent e => new SeatCreated(e.Id),
|
||||
SeatReservedDomainEvent e => new SeatReserved(e.Id),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public IInternalCommand? MapToInternalCommand(IDomainEvent @event)
|
||||
{
|
||||
return @event switch
|
||||
{
|
||||
FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
|
||||
FlightUpdatedDomainEvent e => new UpdateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
|
||||
FlightDeletedDomainEvent e => new DeleteFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
|
||||
AircraftCreatedDomainEvent e => new CreateAircraftMongo(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
|
||||
AirportCreatedDomainEvent e => new CreateAirportMongo(e.Id, e.Name, e.Address, e.Code, e.IsDeleted),
|
||||
SeatCreatedDomainEvent e => new CreateSeatMongo(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
|
||||
SeatReservedDomainEvent e => new ReserveSeatMongo(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.EFCore;
|
||||
using BuildingBlocks.Mongo;
|
||||
using Flight.Data;
|
||||
using Flight.Data.Seed;
|
||||
using Flight.GrpcServer.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Flight.Extensions.Infrastructure;
|
||||
|
||||
|
||||
public static class InfrastructureExtensions
|
||||
{
|
||||
public static WebApplicationBuilder AddFlightModules(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddScoped<IEventMapper, EventMapper>();
|
||||
builder.AddCustomDbContext<FlightDbContext>(nameof(Flight));
|
||||
builder.Services.AddScoped<IDataSeeder, FlightDataSeeder>();
|
||||
builder.AddMongoDbContext<FlightReadDbContext>();
|
||||
|
||||
builder.Services.AddCustomMediatR();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public static WebApplication UseFlightModules(this WebApplication app)
|
||||
{
|
||||
app.UseMigration<FlightDbContext>();
|
||||
app.MapGrpcService<FlightGrpcServices>();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Logging;
|
||||
using BuildingBlocks.Validation;
|
||||
using Flight.Data;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Flight.Extensions.Infrastructure;
|
||||
|
||||
public static class MediatRExtensions
|
||||
{
|
||||
public static IServiceCollection AddCustomMediatR(this IServiceCollection services)
|
||||
{
|
||||
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(typeof(FlightRoot).Assembly));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
|
||||
// services.AddScoped(typeof(IPipelineBehavior<,>), typeof(EfTxFlightBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CachingBehavior<,>));
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(InvalidateCachingBehavior<,>));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.68.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\Migrations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="GrpcServer\Protos\flight.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\building-blocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,6 @@
|
||||
namespace Flight;
|
||||
|
||||
public class FlightRoot
|
||||
{
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Flight.Flights.Dtos;
|
||||
public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price);
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Flights.Enums;
|
||||
|
||||
public enum FlightStatus
|
||||
{
|
||||
Unknown = 0,
|
||||
Flying = 1,
|
||||
Delay = 2,
|
||||
Canceled = 3,
|
||||
Completed = 4
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class FlightAlreadyExistException : ConflictException
|
||||
{
|
||||
public FlightAlreadyExistException(int? code = default) : base("Flight already exist!", code)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class FlightExceptions : BadRequestException
|
||||
{
|
||||
public FlightExceptions(DateTime departureDate, DateTime arriveDate) :
|
||||
base($"Departure date: '{departureDate}' must be before arrive date: '{arriveDate}'.")
|
||||
{ }
|
||||
|
||||
public FlightExceptions(DateTime flightDate) :
|
||||
base($"Flight date: '{flightDate}' must be between departure and arrive dates.")
|
||||
{ }
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
namespace Flight.Flights.Exceptions;
|
||||
|
||||
public class FlightNotFountException : NotFoundException
|
||||
{
|
||||
public FlightNotFountException() : base("Flight not found!")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidArriveDateException : BadRequestException
|
||||
{
|
||||
public InvalidArriveDateException(DateTime arriveDate)
|
||||
: base($"Arrive Date: '{arriveDate}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidDepartureDateException : BadRequestException
|
||||
{
|
||||
public InvalidDepartureDateException(DateTime departureDate)
|
||||
: base($"Departure Date: '{departureDate}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidDurationException : BadRequestException
|
||||
{
|
||||
public InvalidDurationException()
|
||||
: base("Duration cannot be negative.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidFlightDateException : BadRequestException
|
||||
{
|
||||
public InvalidFlightDateException(DateTime flightDate)
|
||||
: base($"Flight Date: '{flightDate}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using System;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidFlightIdException : BadRequestException
|
||||
{
|
||||
public InvalidFlightIdException(Guid flightId)
|
||||
: base($"flightId: '{flightId}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class InvalidFlightNumberException : BadRequestException
|
||||
{
|
||||
public InvalidFlightNumberException(string flightNumber)
|
||||
: base($"Flight Number: '{flightNumber}' is invalid.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Flight.Flights.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
|
||||
public class InvalidPriceException : BadRequestException
|
||||
{
|
||||
public InvalidPriceException()
|
||||
: base($"Price Cannot be negative.")
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,127 @@
|
||||
namespace Flight.Flights.Features.CreatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Aircrafts.ValueObjects;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Web;
|
||||
using Data;
|
||||
using Duende.IdentityServer.EntityFramework.Entities;
|
||||
using Exceptions;
|
||||
using Flight.Airports.ValueObjects;
|
||||
using FluentValidation;
|
||||
using Mapster;
|
||||
using MapsterMapper;
|
||||
using MassTransit;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ValueObjects;
|
||||
|
||||
public record CreateFlight(string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status,
|
||||
decimal Price) : ICommand<CreateFlightResult>, IInternalCommand
|
||||
{
|
||||
public Guid Id { get; init; } = NewId.NextGuid();
|
||||
}
|
||||
|
||||
public record CreateFlightResult(Guid Id);
|
||||
|
||||
public record FlightCreatedDomainEvent(Guid Id, 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 record CreateFlightRequestDto(string FlightNumber, Guid AircraftId, Guid DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price);
|
||||
|
||||
public record CreateFlightResponseDto(Guid Id);
|
||||
|
||||
public class CreateFlightEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight", async (CreateFlightRequestDto request,
|
||||
IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var command = mapper.Map<CreateFlight>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
var response = result.Adapt<CreateFlightResponseDto>();
|
||||
|
||||
return Results.CreatedAtRoute("GetFlightById", new { id = result.Id }, response);
|
||||
})
|
||||
.RequireAuthorization(nameof(ApiScope))
|
||||
.WithName("CreateFlight")
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.Produces<CreateFlightResponseDto>(StatusCodes.Status201Created)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.WithSummary("Create Flight")
|
||||
.WithDescription("Create Flight")
|
||||
.WithOpenApi()
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateFlightValidator : AbstractValidator<CreateFlight>
|
||||
{
|
||||
public CreateFlightValidator()
|
||||
{
|
||||
RuleFor(x => x.Price).GreaterThan(0).WithMessage("Price must be greater than 0");
|
||||
|
||||
RuleFor(x => x.Status).Must(p => (p.GetType().IsEnum &&
|
||||
p == Enums.FlightStatus.Flying) ||
|
||||
p == Enums.FlightStatus.Canceled ||
|
||||
p == Enums.FlightStatus.Delay ||
|
||||
p == Enums.FlightStatus.Completed)
|
||||
.WithMessage("Status must be Flying, Delay, Canceled or Completed");
|
||||
|
||||
RuleFor(x => x.AircraftId).NotEmpty().WithMessage("AircraftId must be not empty");
|
||||
RuleFor(x => x.DepartureAirportId).NotEmpty().WithMessage("DepartureAirportId must be not empty");
|
||||
RuleFor(x => x.ArriveAirportId).NotEmpty().WithMessage("ArriveAirportId must be not empty");
|
||||
RuleFor(x => x.DurationMinutes).GreaterThan(0).WithMessage("DurationMinutes must be greater than 0");
|
||||
RuleFor(x => x.FlightDate).NotEmpty().WithMessage("FlightDate must be not empty");
|
||||
}
|
||||
}
|
||||
|
||||
internal class CreateFlightHandler : ICommandHandler<CreateFlight, CreateFlightResult>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
|
||||
public CreateFlightHandler(FlightDbContext flightDbContext)
|
||||
{
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<CreateFlightResult> Handle(CreateFlight request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == request.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (flight is not null)
|
||||
{
|
||||
throw new FlightAlreadyExistException();
|
||||
}
|
||||
|
||||
var flightEntity = Models.Flight.Create(FlightId.Of(request.Id), FlightNumber.Of(request.FlightNumber), AircraftId.Of(request.AircraftId),
|
||||
AirportId.Of(request.DepartureAirportId), DepartureDate.Of(request.DepartureDate),
|
||||
ArriveDate.Of(request.ArriveDate), AirportId.Of(request.ArriveAirportId), DurationMinutes.Of(request.DurationMinutes), FlightDate.Of(request.FlightDate), request.Status,
|
||||
Price.Of(request.Price));
|
||||
|
||||
var newFlight = (await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken)).Entity;
|
||||
|
||||
return new CreateFlightResult(newFlight.Id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
namespace Flight.Flights.Features.CreatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Exceptions;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
public record CreateFlightMongo(Guid Id, string FlightNumber, Guid AircraftId, DateTime DepartureDate,
|
||||
Guid DepartureAirportId, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted = false) : InternalCommand;
|
||||
|
||||
internal class CreateFlightMongoHandler : ICommandHandler<CreateFlightMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateFlightMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateFlightMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(request);
|
||||
|
||||
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (flight is not null)
|
||||
{
|
||||
throw new FlightAlreadyExistException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Flight.InsertOneAsync(flightReadModel, cancellationToken: cancellationToken);
|
||||
|
||||
return Unit.Value;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user