mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-10 17:59:38 +08:00
refactor: Refactor feature folder structure in vertical slice architecture
This commit is contained in:
parent
414ae0d305
commit
249b3015b5
@ -5,8 +5,8 @@
|
||||
"elements": [
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 240,
|
||||
"versionNonce": 1959093945,
|
||||
"version": 242,
|
||||
"versionNonce": 1509780320,
|
||||
"isDeleted": false,
|
||||
"id": "80OGzNPG6Gk8NAvbV3XaF",
|
||||
"fillStyle": "solid",
|
||||
@ -18,7 +18,7 @@
|
||||
"x": 648,
|
||||
"y": 187,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#41a7f5",
|
||||
"backgroundColor": "#a8bffe",
|
||||
"width": 538,
|
||||
"height": 62,
|
||||
"seed": 246982778,
|
||||
@ -32,14 +32,14 @@
|
||||
"id": "46GLDhDwmnc8RGy3v8OK8"
|
||||
}
|
||||
],
|
||||
"updated": 1678375014228,
|
||||
"updated": 1679316672934,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 136,
|
||||
"versionNonce": 833895767,
|
||||
"version": 137,
|
||||
"versionNonce": 703919968,
|
||||
"isDeleted": false,
|
||||
"id": "46GLDhDwmnc8RGy3v8OK8",
|
||||
"fillStyle": "hachure",
|
||||
@ -58,7 +58,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1678375014228,
|
||||
"updated": 1679315949309,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -71,8 +71,8 @@
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 350,
|
||||
"versionNonce": 435319161,
|
||||
"version": 358,
|
||||
"versionNonce": 356515488,
|
||||
"isDeleted": false,
|
||||
"id": "nZuYK7wbLObwRvpRRLHay",
|
||||
"fillStyle": "solid",
|
||||
@ -84,7 +84,7 @@
|
||||
"x": 648,
|
||||
"y": 263,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#ced4da",
|
||||
"backgroundColor": "#fea8d5",
|
||||
"width": 538,
|
||||
"height": 62,
|
||||
"seed": 287502970,
|
||||
@ -98,14 +98,14 @@
|
||||
"id": "OALII-DXtatRPgn_EkHfp"
|
||||
}
|
||||
],
|
||||
"updated": 1678375286769,
|
||||
"updated": 1679316735759,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 245,
|
||||
"versionNonce": 63658455,
|
||||
"version": 246,
|
||||
"versionNonce": 1126108000,
|
||||
"isDeleted": false,
|
||||
"id": "OALII-DXtatRPgn_EkHfp",
|
||||
"fillStyle": "hachure",
|
||||
@ -124,7 +124,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1678375018983,
|
||||
"updated": 1679315949309,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -137,8 +137,8 @@
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 276,
|
||||
"versionNonce": 498715799,
|
||||
"version": 282,
|
||||
"versionNonce": 787808928,
|
||||
"isDeleted": false,
|
||||
"id": "za_4vz64MSfPF5TWmD7wj",
|
||||
"fillStyle": "solid",
|
||||
@ -150,7 +150,7 @@
|
||||
"x": 650,
|
||||
"y": 338,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fa5252",
|
||||
"backgroundColor": "#f30358",
|
||||
"width": 538,
|
||||
"height": 62,
|
||||
"seed": 676018342,
|
||||
@ -164,14 +164,14 @@
|
||||
"id": "6CqYCSOKHqhqJ8nf4b-Sv"
|
||||
}
|
||||
],
|
||||
"updated": 1678375038578,
|
||||
"updated": 1679316783390,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 188,
|
||||
"versionNonce": 145052535,
|
||||
"version": 189,
|
||||
"versionNonce": 1441177440,
|
||||
"isDeleted": false,
|
||||
"id": "6CqYCSOKHqhqJ8nf4b-Sv",
|
||||
"fillStyle": "hachure",
|
||||
@ -190,7 +190,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1678375033581,
|
||||
"updated": 1679315949309,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -203,8 +203,8 @@
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 324,
|
||||
"versionNonce": 1540569401,
|
||||
"version": 326,
|
||||
"versionNonce": 1669046112,
|
||||
"isDeleted": false,
|
||||
"id": "t2sZwLLvmq3y2ndIbEomB",
|
||||
"fillStyle": "solid",
|
||||
@ -216,7 +216,7 @@
|
||||
"x": 648,
|
||||
"y": 413,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#07da63",
|
||||
"backgroundColor": "#9d9ca2",
|
||||
"width": 538,
|
||||
"height": 62,
|
||||
"seed": 1173221990,
|
||||
@ -230,14 +230,14 @@
|
||||
"id": "b3wdaWjaVmgHpzMD26uKD"
|
||||
}
|
||||
],
|
||||
"updated": 1678375048801,
|
||||
"updated": 1679316844215,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 223,
|
||||
"versionNonce": 1635337382,
|
||||
"version": 224,
|
||||
"versionNonce": 1385935712,
|
||||
"isDeleted": false,
|
||||
"id": "b3wdaWjaVmgHpzMD26uKD",
|
||||
"fillStyle": "hachure",
|
||||
@ -256,7 +256,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1677440080893,
|
||||
"updated": 1679315949310,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -269,20 +269,20 @@
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 191,
|
||||
"versionNonce": 502145367,
|
||||
"version": 202,
|
||||
"versionNonce": 1461187232,
|
||||
"isDeleted": false,
|
||||
"id": "FQZImjU2-VUOATU9Yeyly",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 90,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 678,
|
||||
"y": 154,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fff700 ",
|
||||
"backgroundColor": "#fefda8",
|
||||
"width": 48,
|
||||
"height": 361,
|
||||
"seed": 1254939642,
|
||||
@ -291,26 +291,26 @@
|
||||
"type": 3
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1678375337365,
|
||||
"updated": 1679316609154,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
"version": 231,
|
||||
"versionNonce": 579313209,
|
||||
"version": 249,
|
||||
"versionNonce": 1540775776,
|
||||
"isDeleted": false,
|
||||
"id": "_Vw9EnXAyzxRDEzXCTfeL",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 90,
|
||||
"opacity": 100,
|
||||
"angle": 0,
|
||||
"x": 743,
|
||||
"x": 742,
|
||||
"y": 153.5,
|
||||
"strokeColor": "#000000",
|
||||
"backgroundColor": "#fff700 ",
|
||||
"backgroundColor": "#fefda8",
|
||||
"width": 48,
|
||||
"height": 361,
|
||||
"seed": 523058342,
|
||||
@ -319,21 +319,21 @@
|
||||
"type": 3
|
||||
},
|
||||
"boundElements": [],
|
||||
"updated": 1678375355851,
|
||||
"updated": 1679316594766,
|
||||
"link": null,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 245,
|
||||
"versionNonce": 1220596518,
|
||||
"version": 249,
|
||||
"versionNonce": 871687840,
|
||||
"isDeleted": false,
|
||||
"id": "hyJiOwPt7LFndn5R0xgfL",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 70,
|
||||
"opacity": 100,
|
||||
"angle": 4.707547804955119,
|
||||
"x": 637.1248451774691,
|
||||
"y": 317.9455509364301,
|
||||
@ -345,7 +345,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1677440080893,
|
||||
"updated": 1679315961675,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -358,15 +358,15 @@
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 179,
|
||||
"versionNonce": 1053153850,
|
||||
"version": 182,
|
||||
"versionNonce": 1494113120,
|
||||
"isDeleted": false,
|
||||
"id": "7KOHd5JA_wVMmwXPVT1N3",
|
||||
"fillStyle": "solid",
|
||||
"strokeWidth": 1,
|
||||
"strokeStyle": "solid",
|
||||
"roughness": 1,
|
||||
"opacity": 70,
|
||||
"opacity": 80,
|
||||
"angle": 4.7123889803846915,
|
||||
"x": 695.6880416870117,
|
||||
"y": 313.20000000000005,
|
||||
@ -378,7 +378,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1677440080893,
|
||||
"updated": 1679315949310,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
@ -391,8 +391,8 @@
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 162,
|
||||
"versionNonce": 1556577894,
|
||||
"version": 163,
|
||||
"versionNonce": 1243581088,
|
||||
"isDeleted": false,
|
||||
"id": "SuFNrbzZGowiIybusnadN",
|
||||
"fillStyle": "solid",
|
||||
@ -411,7 +411,7 @@
|
||||
"groupIds": [],
|
||||
"roundness": null,
|
||||
"boundElements": [],
|
||||
"updated": 1677440080893,
|
||||
"updated": 1679315949310,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 28,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
@ -26,10 +26,14 @@
|
||||
<Protobuf Include="GrpcClient\Protos\passenger.proto" GrpcServices="Both" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="GrpcClient\Protos" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Integration.Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class BookingAlreadyExistException : ConflictException
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Booking.Booking.Exceptions;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class FlightNotFoundException : NotFoundException
|
||||
{
|
||||
@ -1,10 +1,10 @@
|
||||
using Booking.Booking.Dtos;
|
||||
using Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
using Booking.Booking.Features.CreateBooking.Dtos.V1;
|
||||
using Mapster;
|
||||
|
||||
namespace Booking.Booking.Features;
|
||||
|
||||
using CreatingBook.Commands.V1;
|
||||
|
||||
public class BookingMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
@ -17,7 +17,7 @@ public class BookingMappings : IRegister
|
||||
x.Trip.Description));
|
||||
|
||||
|
||||
config.NewConfig<CreateBookingRequestDto, CreateBookingCommand>()
|
||||
.ConstructUsing(x => new CreateBookingCommand(x.PassengerId, x.FlightId, x.Description));
|
||||
config.NewConfig<CreateBookingRequestDto, CreateBooking>()
|
||||
.ConstructUsing(x => new CreateBooking(x.PassengerId, x.FlightId, x.Description));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
|
||||
public record CreateBookingCommand(long PassengerId, long FlightId, string Description) : ICommand<ulong>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
|
||||
public class CreateBookingCommandValidator : AbstractValidator<CreateBookingCommand>
|
||||
{
|
||||
public CreateBookingCommandValidator()
|
||||
{
|
||||
CascadeMode = CascadeMode.Stop;
|
||||
|
||||
RuleFor(x => x.FlightId).NotNull().WithMessage("FlightId is required!");
|
||||
RuleFor(x => x.PassengerId).NotNull().WithMessage("PassengerId is required!");
|
||||
}
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Booking.Booking.Features.CreateBooking.Dtos.V1;
|
||||
|
||||
public record CreateBookingRequestDto(long PassengerId, long FlightId, string Description);
|
||||
@ -1,7 +1,7 @@
|
||||
using Booking.Booking.Models.ValueObjects;
|
||||
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.Core.Model;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Events.Domain.V1;
|
||||
using Models.ValueObjects;
|
||||
|
||||
public record BookingCreatedDomainEvent(long Id, PassengerInfo PassengerInfo, Trip Trip) : Audit, IDomainEvent;
|
||||
@ -1,16 +1,33 @@
|
||||
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
using Ardalis.GuardClauses;
|
||||
using Booking.Booking.Features.CreateBooking.Exceptions;
|
||||
using Booking.Booking.Models.ValueObjects;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.EventStoreDB.Repository;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using BuildingBlocks.Web;
|
||||
using Exceptions;
|
||||
using Flight;
|
||||
using FluentValidation;
|
||||
using Models.ValueObjects;
|
||||
using Passenger;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
public record CreateBooking(long PassengerId, long FlightId, string Description) : ICommand<ulong>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, ulong>
|
||||
internal 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, ulong>
|
||||
{
|
||||
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
|
||||
private readonly ICurrentUserProvider _currentUserProvider;
|
||||
@ -31,26 +48,30 @@ public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand,
|
||||
_passengerGrpcServiceClient = passengerGrpcServiceClient;
|
||||
}
|
||||
|
||||
public async Task<ulong> Handle(CreateBookingCommand command, CancellationToken cancellationToken)
|
||||
public async Task<ulong> Handle(CreateBooking command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var flight = await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest {Id = command.FlightId});
|
||||
var flight = await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest { Id = command.FlightId });
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFoundException();
|
||||
}
|
||||
|
||||
var passenger =
|
||||
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest {Id = command.PassengerId});
|
||||
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest { Id = command.PassengerId });
|
||||
|
||||
var emptySeat = (await _flightGrpcServiceClient
|
||||
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest {FlightId = command.FlightId}).ResponseAsync)
|
||||
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest { FlightId = command.FlightId }).ResponseAsync)
|
||||
?.Items?.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, new PassengerInfo(passenger.Name), new Trip(
|
||||
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,
|
||||
@ -1,29 +1,26 @@
|
||||
using Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
using Booking.Booking.Features.CreateBooking.Dtos.V1;
|
||||
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
using BuildingBlocks.Web;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Booking.Booking.Features.CreateBooking.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
public record CreateBookingRequestDto(long PassengerId, long FlightId, string Description);
|
||||
|
||||
public class CreateBookingEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapPost($"{EndpointConfig.BaseApiPath}/booking", CreateBooking)
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/booking", CreateBooking)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Booking")
|
||||
.WithName("CreateBooking")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Booking").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Booking").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status200OK,
|
||||
@ -41,13 +38,13 @@ public class CreateBookingEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> CreateBooking(CreateBookingRequestDto request, IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = mapper.Map<CreateBookingCommand>(request);
|
||||
var command = mapper.Map<CreateBooking>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
|
||||
using Booking.Booking.Models.ValueObjects;
|
||||
using BuildingBlocks.EventStoreDB.Events;
|
||||
|
||||
namespace Booking.Booking.Models;
|
||||
|
||||
using Features.CreatingBook.Commands.V1;
|
||||
|
||||
public record Booking : AggregateEventSourcing<long>
|
||||
{
|
||||
public Trip Trip { get; private set; }
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
|
||||
using Booking.Booking.Models.Reads;
|
||||
using Booking.Data;
|
||||
using BuildingBlocks.EventStoreDB.Events;
|
||||
@ -10,6 +9,8 @@ using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Booking;
|
||||
|
||||
using Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
public class BookingProjection : IProjectionProcessor
|
||||
{
|
||||
private readonly BookingReadDbContext _bookingReadDbContext;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Booking;
|
||||
|
||||
using Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
using AutoBogus;
|
||||
using Booking.Booking.Features.CreateBooking;
|
||||
using Booking.Booking.Features.CreateBooking.Commands.V1;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
|
||||
namespace Integration.Test.Fakes;
|
||||
|
||||
public sealed class FakeCreateBookingCommand : AutoFaker<CreateBookingCommand>
|
||||
using global::Booking.Booking.Features.CreatingBook.Commands.V1;
|
||||
|
||||
public sealed class FakeCreateBookingCommand : AutoFaker<CreateBooking>
|
||||
{
|
||||
public FakeCreateBookingCommand()
|
||||
{
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
namespace Flight.Aircrafts.Dtos;
|
||||
|
||||
public record AircraftDto(long Id, string Name, string Model, int ManufacturingYear);
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Flight.Aircrafts.Dtos;
|
||||
|
||||
public record AircraftResponseDto(long Id, string Name, string Model, int ManufacturingYear);
|
||||
@ -1,8 +1,8 @@
|
||||
namespace Flight.Aircrafts.Exceptions;
|
||||
|
||||
using System.Net;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Exceptions;
|
||||
|
||||
public class AircraftAlreadyExistException : AppException
|
||||
{
|
||||
public AircraftAlreadyExistException() : base("Aircraft already exist!", HttpStatusCode.Conflict)
|
||||
@ -1,19 +1,16 @@
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Dtos.V1;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Aircrafts.Models.Reads;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
using Mapster;
|
||||
|
||||
namespace Flight.Aircrafts.Features;
|
||||
|
||||
using CreatingAircraft.V1;
|
||||
|
||||
public class AircraftMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<CreateAircraftMongoCommand, AircraftReadModel>()
|
||||
config.NewConfig<CreateAircraftMongo, AircraftReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AircraftId, s => s.Id);
|
||||
|
||||
@ -21,7 +18,7 @@ public class AircraftMappings : IRegister
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AircraftId, s => s.Id);
|
||||
|
||||
config.NewConfig<CreateAircraftRequestDto, CreateAircraftCommand>()
|
||||
.ConstructUsing(x => new CreateAircraftCommand(x.Name, x.Model, x.ManufacturingYear));
|
||||
config.NewConfig<CreateAircraftRequestDto, CreatingAircraft.V1.CreateAircraft>()
|
||||
.ConstructUsing(x => new CreatingAircraft.V1.CreateAircraft(x.Name, x.Model, x.ManufacturingYear));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Aircrafts.Dtos;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1;
|
||||
|
||||
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : ICommand<AircraftResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using Flight.Aircrafts.Dtos;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Exceptions;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Data;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1;
|
||||
|
||||
public class CreateAircraftCommandHandler : IRequestHandler<CreateAircraftCommand, AircraftResponseDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAircraftCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<AircraftResponseDto> Handle(CreateAircraftCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var aircraft = await _flightDbContext.Aircraft.SingleOrDefaultAsync(x => x.Model == command.Model, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
throw new AircraftAlreadyExistException();
|
||||
|
||||
var aircraftEntity = Aircraft.Create(command.Id, command.Name, command.Model, command.ManufacturingYear);
|
||||
|
||||
var newAircraft = await _flightDbContext.Aircraft.AddAsync(aircraftEntity, cancellationToken);
|
||||
|
||||
await _flightDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return _mapper.Map<AircraftResponseDto>(newAircraft.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1;
|
||||
|
||||
public class CreateAircraftCommandValidator : AbstractValidator<CreateAircraftCommand>
|
||||
{
|
||||
public CreateAircraftCommandValidator()
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
|
||||
|
||||
public record CreateAircraftMongoCommand(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : InternalCommand;
|
||||
@ -1,4 +0,0 @@
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Dtos.V1;
|
||||
|
||||
public record CreateAircraftRequestDto(string Name, string Model, int ManufacturingYear);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Events.Domain.V1;
|
||||
|
||||
public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent;
|
||||
@ -0,0 +1,63 @@
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Exceptions;
|
||||
using Flight.Aircrafts.Dtos;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Data;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public record CreateAircraft(string Name, string Model, int ManufacturingYear) : ICommand<AircraftDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
internal 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, AircraftDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAircraftHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<AircraftDto> Handle(CreateAircraft request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var aircraft = await _flightDbContext.Aircraft.SingleOrDefaultAsync(x => x.Model == request.Model, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AircraftAlreadyExistException();
|
||||
}
|
||||
|
||||
var aircraftEntity = Aircraft.Create(request.Id, request.Name, request.Model, request.ManufacturingYear);
|
||||
|
||||
var newAircraft = await _flightDbContext.Aircraft.AddAsync(aircraftEntity, cancellationToken);
|
||||
|
||||
await _flightDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return _mapper.Map<AircraftDto>(newAircraft.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Aircrafts.Dtos;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Dtos.V1;
|
||||
using Dtos;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -11,25 +12,23 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
public record CreateAircraftRequestDto(string Name, string Model, int ManufacturingYear);
|
||||
|
||||
public class CreateAircraftEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", CreateAircraft)
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/aircraft", CreateAircraft)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("CreateAircraft")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status200OK,
|
||||
"Aircraft Created",
|
||||
typeof(AircraftResponseDto)))
|
||||
typeof(AircraftDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -42,13 +41,13 @@ public class CreateAircraftEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> CreateAircraft(CreateAircraftRequestDto request, IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = mapper.Map<CreateAircraftCommand>(request);
|
||||
var command = mapper.Map<CreateAircraft>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Exceptions;
|
||||
using Flight.Aircrafts.Models.Reads;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Exceptions;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Data;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
|
||||
public record CreateAircraftMongo(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : InternalCommand;
|
||||
|
||||
public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftMongoCommand>
|
||||
public class CreateAircraftMongoHandler : ICommandHandler<CreateAircraftMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAircraftMongoCommandHandler(
|
||||
public CreateAircraftMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
@ -25,17 +28,19 @@ public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftM
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateAircraftMongoCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Unit> Handle(CreateAircraftMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var aircraftReadModel = _mapper.Map<AircraftReadModel>(command);
|
||||
var aircraftReadModel = _mapper.Map<AircraftReadModel>(request);
|
||||
|
||||
var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AircraftAlreadyExistException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken);
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
using BuildingBlocks.Core.Model;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Events.Domain.V1;
|
||||
|
||||
namespace Flight.Aircrafts.Models;
|
||||
|
||||
using Features.CreatingAircraft.V1;
|
||||
|
||||
public record Aircraft : Aggregate<long>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Flight.Aircrafts.Models.Reads;
|
||||
namespace Flight.Aircrafts.Models;
|
||||
|
||||
public class AircraftReadModel
|
||||
{
|
||||
@ -1,26 +0,0 @@
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
|
||||
using Flight.Airports.Features.CreateAirport.Dtos.V1;
|
||||
using Flight.Airports.Models;
|
||||
using Flight.Airports.Models.Reads;
|
||||
using Mapster;
|
||||
|
||||
namespace Flight.Airports;
|
||||
|
||||
public class AirportMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<CreateAirportMongoCommand, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<Airport, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<CreateAirportRequestDto, CreateAirportCommand>()
|
||||
.ConstructUsing(x => new CreateAirportCommand(x.Name, x.Address, x.Code));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
namespace Flight.Airports.Dtos;
|
||||
|
||||
public record AirportDto(long Id, string Name, string Address, string Code);
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Flight.Airports.Dtos;
|
||||
|
||||
public record AirportResponseDto(long Id, string Name, string Address, string Code);
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Flight.Airports.Exceptions;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class AirportAlreadyExistException : ConflictException
|
||||
{
|
||||
@ -0,0 +1,23 @@
|
||||
namespace Flight.Airports.Features;
|
||||
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Airports.Features.CreatingAirport.V1;
|
||||
using Flight.Airports.Models;
|
||||
using Mapster;
|
||||
|
||||
public class AirportMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<CreateAirportMongo, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<Airport, AirportReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.AirportId, s => s.Id);
|
||||
|
||||
config.NewConfig<CreateAirportRequestDto, CreateAirport>()
|
||||
.ConstructUsing(x => new CreateAirport(x.Name, x.Address, x.Code));
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Airports.Dtos;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
|
||||
public record CreateAirportCommand(string Name, string Address, string Code) : ICommand<AirportResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using Flight.Airports.Dtos;
|
||||
using Flight.Airports.Features.CreateAirport.Exceptions;
|
||||
using Flight.Data;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
|
||||
public class CreateAirportCommandHandler : IRequestHandler<CreateAirportCommand, AirportResponseDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAirportCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<AirportResponseDto> Handle(CreateAirportCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var airport = await _flightDbContext.Airports.SingleOrDefaultAsync(x => x.Code == command.Code, cancellationToken);
|
||||
|
||||
if (airport is not null)
|
||||
throw new AirportAlreadyExistException();
|
||||
|
||||
var airportEntity = Models.Airport.Create(command.Id, command.Name, command.Code, command.Address);
|
||||
|
||||
var newAirport = await _flightDbContext.Airports.AddAsync(airportEntity, cancellationToken);
|
||||
|
||||
await _flightDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return _mapper.Map<AirportResponseDto>(newAirport.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
|
||||
public class CreateAirportCommandValidator : AbstractValidator<CreateAirportCommand>
|
||||
{
|
||||
public CreateAirportCommandValidator()
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
|
||||
|
||||
public record CreateAirportMongoCommand(long Id, string Name, string Address, string Code, bool IsDeleted) : InternalCommand;
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Flight.Airports.Features.CreateAirport.Dtos.V1;
|
||||
|
||||
public record CreateAirportRequestDto(string Name, string Address, string Code);
|
||||
@ -1,5 +1,5 @@
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Events.Domain.V1;
|
||||
|
||||
public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code, bool IsDeleted) : IDomainEvent;
|
||||
@ -0,0 +1,63 @@
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Dtos;
|
||||
using Exceptions;
|
||||
using Data;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public record CreateAirport(string Name, string Address, string Code) : ICommand<AirportDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
internal 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, AirportDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAirportHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<AirportDto> Handle(CreateAirport request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var airport = await _flightDbContext.Airports.SingleOrDefaultAsync(x => x.Code == request.Code, cancellationToken);
|
||||
|
||||
if (airport is not null)
|
||||
{
|
||||
throw new AirportAlreadyExistException();
|
||||
}
|
||||
|
||||
var airportEntity = Models.Airport.Create(request.Id, request.Name, request.Code, request.Address);
|
||||
|
||||
var newAirport = await _flightDbContext.Airports.AddAsync(airportEntity, cancellationToken);
|
||||
|
||||
await _flightDbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return _mapper.Map<AirportDto>(newAirport.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Airports.Dtos;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1;
|
||||
using Flight.Airports.Features.CreateAirport.Dtos.V1;
|
||||
using Dtos;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -11,25 +12,23 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
public record CreateAirportRequestDto(string Name, string Address, string Code);
|
||||
|
||||
public class CreateAirportEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", CreateAirport)
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight/airport", CreateAirport)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("CreateAirport")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status200OK,
|
||||
"Airport Created",
|
||||
typeof(AirportResponseDto)))
|
||||
typeof(AirportDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -42,13 +41,13 @@ public class CreateAirportEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> CreateAirport(CreateAirportRequestDto request, IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = mapper.Map<CreateAirportCommand>(request);
|
||||
var command = mapper.Map<CreateAirport>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Airports.Features.CreatingAirport.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Airports.Features.CreateAirport.Exceptions;
|
||||
using Flight.Airports.Models.Reads;
|
||||
using Flight.Data;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Exceptions;
|
||||
using Models;
|
||||
using Data;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
|
||||
public record CreateAirportMongo(long Id, string Name, string Address, string Code, bool IsDeleted) : InternalCommand;
|
||||
|
||||
public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMongoCommand>
|
||||
internal class CreateAirportMongoHandler : ICommandHandler<CreateAirportMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateAirportMongoCommandHandler(
|
||||
public CreateAirportMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
@ -25,17 +28,19 @@ public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMon
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateAirportMongoCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Unit> Handle(CreateAirportMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var airportReadModel = _mapper.Map<AirportReadModel>(command);
|
||||
var airportReadModel = _mapper.Map<AirportReadModel>(request);
|
||||
|
||||
var aircraft = await _flightReadDbContext.Airport.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.AirportId == airportReadModel.AirportId, cancellationToken);
|
||||
|
||||
if (aircraft is not null)
|
||||
{
|
||||
throw new AirportAlreadyExistException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
using BuildingBlocks.Core.Model;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Airports.Features.CreateAirport.Events.Domain.V1;
|
||||
|
||||
namespace Flight.Airports.Models;
|
||||
|
||||
using Features.CreatingAirport.V1;
|
||||
|
||||
public record Airport : Aggregate<long>
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Flight.Airports.Models.Reads;
|
||||
namespace Flight.Airports.Models;
|
||||
|
||||
public class AirportReadModel
|
||||
{
|
||||
@ -1,14 +1,15 @@
|
||||
using BuildingBlocks.Mongo;
|
||||
using Flight.Aircrafts.Models.Reads;
|
||||
using Flight.Airports.Models.Reads;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using Flight.Seats.Models.Reads;
|
||||
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)
|
||||
|
||||
@ -3,12 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.EFCore;
|
||||
using Flight.Aircrafts.Models;
|
||||
using Flight.Aircrafts.Models.Reads;
|
||||
using Flight.Airports.Models;
|
||||
using Flight.Airports.Models.Reads;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using Flight.Seats.Models;
|
||||
using Flight.Seats.Models.Reads;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MongoDB.Driver;
|
||||
@ -16,6 +12,8 @@ using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Data.Seed;
|
||||
|
||||
using Flights.Models;
|
||||
|
||||
public class FlightDataSeeder : IDataSeeder
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
|
||||
@ -1,23 +1,17 @@
|
||||
using BuildingBlocks.Contracts.EventBus.Messages;
|
||||
using BuildingBlocks.Core;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
|
||||
using Flight.Aircrafts.Features.CreateAircraft.Events.Domain.V1;
|
||||
using Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
|
||||
using Flight.Airports.Features.CreateAirport.Events.Domain.V1;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.CreateFlight.Events.Domain.V1;
|
||||
using Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
|
||||
using Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.UpdateFlight.Events.V1;
|
||||
using Flight.Seats.Features.CreateSeat.Commands.V1.Reads;
|
||||
using Flight.Seats.Features.CreateSeat.Events.Domain.V1;
|
||||
using Flight.Seats.Features.ReserveSeat.Commands.V1.Reads;
|
||||
using Flight.Seats.Features.ReserveSeat.Events.Domain.V1;
|
||||
|
||||
namespace Flight;
|
||||
|
||||
using Aircrafts.Features.CreatingAircraft.V1;
|
||||
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.Commands.V1;
|
||||
|
||||
// ref: https://www.ledjonbehluli.com/posts/domain_to_integration_event/
|
||||
public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
@ -40,16 +34,16 @@ public sealed class EventMapper : IEventMapper
|
||||
{
|
||||
return @event switch
|
||||
{
|
||||
FlightCreatedDomainEvent e => new CreateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
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 UpdateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
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 DeleteFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||
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 CreateAircraftMongoCommand(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
|
||||
AirportCreatedDomainEvent e => new CreateAirportMongoCommand(e.Id, e.Name, e.Address, e.Code, e.IsDeleted),
|
||||
SeatCreatedDomainEvent e => new CreateSeatMongoCommand(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
|
||||
SeatReservedDomainEvent e => new ReserveSeatMongoCommand(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, 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
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Data\Migrations" />
|
||||
<Folder Include="Seats\Features\CreateSeat\Events\Domain" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -30,4 +29,20 @@
|
||||
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Unit.Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Integration.Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>EndToEnd.Test</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -3,6 +3,6 @@ using Flight.Flights.Models;
|
||||
|
||||
namespace Flight.Flights.Dtos;
|
||||
|
||||
public record FlightResponseDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
public record FlightDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price);
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Flight.Flights.Exceptions;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class FlightAlreadyExistException : ConflictException
|
||||
{
|
||||
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Flights.Dtos;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
|
||||
public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using Flight.Flights.Features.CreateFlight.Exceptions;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
|
||||
public class CreateFlightCommandHandler : ICommandHandler<CreateFlightCommand, FlightResponseDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateFlightCommandHandler(IMapper mapper,
|
||||
FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightResponseDto> Handle(CreateFlightCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == command.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (flight is not null)
|
||||
throw new FlightAlreadyExistException();
|
||||
|
||||
var flightEntity = Models.Flight.Create(command.Id, command.FlightNumber, command.AircraftId,
|
||||
command.DepartureAirportId, command.DepartureDate,
|
||||
command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, command.Status,
|
||||
command.Price);
|
||||
|
||||
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);
|
||||
|
||||
return _mapper.Map<FlightResponseDto>(newFlight.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
|
||||
public class CreateFlightCommandValidator : AbstractValidator<CreateFlightCommand>
|
||||
{
|
||||
public CreateFlightCommandValidator()
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
|
||||
public record CreateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
@ -1,7 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Dtos.V1;
|
||||
|
||||
public record CreateFlightRequestDto(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price);
|
||||
@ -0,0 +1,78 @@
|
||||
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 BuildingBlocks.IdsGenerator;
|
||||
using Data;
|
||||
using Dtos;
|
||||
using Exceptions;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public record CreateFlight(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price) : ICommand<FlightDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
|
||||
internal 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, FlightDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateFlightHandler(IMapper mapper,
|
||||
FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightDto> 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(request.Id, request.FlightNumber, request.AircraftId,
|
||||
request.DepartureAirportId, request.DepartureDate,
|
||||
request.ArriveDate, request.ArriveAirportId, request.DurationMinutes, request.FlightDate, request.Status,
|
||||
request.Price);
|
||||
|
||||
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);
|
||||
|
||||
return _mapper.Map<FlightDto>(newFlight.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
namespace Flight.Flights.Features.CreatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
using Flight.Flights.Features.CreateFlight.Dtos.V1;
|
||||
using Mapster;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -12,25 +13,25 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
public record CreateFlightRequestDto(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||
decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price);
|
||||
|
||||
public class CreateFlightEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapPost($"{EndpointConfig.BaseApiPath}/flight", CreateFlight)
|
||||
builder.MapPost($"{EndpointConfig.BaseApiPath}/flight", CreateFlight)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("CreateFlight")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status201Created,
|
||||
"Flight Created",
|
||||
typeof(FlightResponseDto)))
|
||||
typeof(FlightDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -43,13 +44,13 @@ public class CreateFlightEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> CreateFlight(CreateFlightRequestDto request, IMediator mediator, IMapper mapper,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var command = mapper.Map<CreateFlightCommand>(request);
|
||||
var command = mapper.Map<CreateFlight>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
@ -1,24 +1,29 @@
|
||||
using System.Threading;
|
||||
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 Flight.Data;
|
||||
using Flight.Flights.Exceptions;
|
||||
using Flight.Flights.Features.CreateFlight.Exceptions;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
public record CreateFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
|
||||
public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongoCommand>
|
||||
internal class CreateFlightMongoHandler : ICommandHandler<CreateFlightMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateFlightMongoCommandHandler(
|
||||
public CreateFlightMongoHandler(
|
||||
FlightReadDbContext flightReadDbContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
@ -26,17 +31,19 @@ public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongo
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(CreateFlightMongoCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Unit> Handle(CreateFlightMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||
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);
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
namespace Flight.Flights.Features.CreatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Flights.Features.CreateFlight.Events.Domain.V1;
|
||||
|
||||
public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
||||
@ -1,7 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Flight.Flights.Dtos;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Commands.V1;
|
||||
|
||||
public record DeleteFlightCommand(long Id) : ICommand<FlightResponseDto>, IInternalCommand;
|
||||
@ -1,14 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Commands.V1;
|
||||
|
||||
public class DeleteFlightCommandValidator : AbstractValidator<DeleteFlightCommand>
|
||||
{
|
||||
public DeleteFlightCommandValidator()
|
||||
{
|
||||
CascadeMode = CascadeMode.Stop;
|
||||
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
|
||||
|
||||
public record DeleteFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
@ -1,35 +1,48 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Flights.Features.DeletingFlight.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Dtos;
|
||||
using Exceptions;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Commands.V1;
|
||||
public record DeleteFlight(long Id) : ICommand<FlightDto>, IInternalCommand;
|
||||
|
||||
public class DeleteFlightCommandHandler : ICommandHandler<DeleteFlightCommand, FlightResponseDto>
|
||||
internal class DeleteFlightValidator : AbstractValidator<DeleteFlight>
|
||||
{
|
||||
public DeleteFlightValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DeleteFlightHandler : ICommandHandler<DeleteFlight, FlightDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public DeleteFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
public DeleteFlightHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightResponseDto> Handle(DeleteFlightCommand command, CancellationToken cancellationToken)
|
||||
public async Task<FlightDto> Handle(DeleteFlight request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == command.Id, cancellationToken);
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
|
||||
}
|
||||
|
||||
var deleteFlight = _flightDbContext.Flights.Remove(flight).Entity;
|
||||
|
||||
@ -37,6 +50,6 @@ public class DeleteFlightCommandHandler : ICommandHandler<DeleteFlightCommand, F
|
||||
deleteFlight.DepartureDate, deleteFlight.ArriveDate, deleteFlight.ArriveAirportId, deleteFlight.DurationMinutes,
|
||||
deleteFlight.FlightDate, deleteFlight.Status, deleteFlight.Price);
|
||||
|
||||
return _mapper.Map<FlightResponseDto>(deleteFlight);
|
||||
return _mapper.Map<FlightDto>(deleteFlight);
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,31 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Flights.Features.DeletingFlight.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.DeleteFlight.Commands.V1;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
|
||||
public class DeleteFlightEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", DeleteFlight)
|
||||
builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", DeleteFlight)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("DeleteFlight")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status204NoContent,
|
||||
"Flight Deleted",
|
||||
typeof(FlightResponseDto)))
|
||||
typeof(FlightDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -40,12 +38,12 @@ public class DeleteFlightEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> DeleteFlight(long id, IMediator mediator, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await mediator.Send(new DeleteFlightCommand(id), cancellationToken);
|
||||
var result = await mediator.Send(new DeleteFlight(id), cancellationToken);
|
||||
|
||||
return Results.NoContent();
|
||||
}
|
||||
@ -1,18 +1,24 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Flights.Features.DeletingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Exceptions;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Exceptions;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
|
||||
public record DeleteFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
|
||||
public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongoCommand>
|
||||
internal class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
@ -25,17 +31,19 @@ public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongo
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(DeleteFlightMongoCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Unit> Handle(DeleteFlightMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(request);
|
||||
|
||||
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Flight.UpdateOneAsync(
|
||||
x => x.FlightId == flightReadModel.FlightId,
|
||||
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
namespace Flight.Flights.Features.DeletingFlight.V1;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||
@ -1,27 +1,23 @@
|
||||
using AutoMapper;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.CreateFlight.Dtos.V1;
|
||||
using Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.UpdateFlight.Commands.V1;
|
||||
using Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
|
||||
using Flight.Flights.Features.UpdateFlight.Dtos;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using Mapster;
|
||||
|
||||
namespace Flight.Flights.Features;
|
||||
|
||||
using CreatingFlight.V1;
|
||||
using DeletingFlight.V1;
|
||||
using Models;
|
||||
using UpdatingFlight.V1;
|
||||
|
||||
public class FlightMappings : IRegister
|
||||
{
|
||||
public void Register(TypeAdapterConfig config)
|
||||
{
|
||||
config.NewConfig<Models.Flight, FlightResponseDto>()
|
||||
.ConstructUsing(x => new FlightResponseDto(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate,
|
||||
config.NewConfig<Models.Flight, FlightDto>()
|
||||
.ConstructUsing(x => new FlightDto(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate,
|
||||
x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.Price));
|
||||
|
||||
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>()
|
||||
config.NewConfig<CreateFlightMongo, FlightReadModel>()
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
|
||||
@ -29,21 +25,21 @@ public class FlightMappings : IRegister
|
||||
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
|
||||
config.NewConfig<FlightReadModel, FlightResponseDto>()
|
||||
config.NewConfig<FlightReadModel, FlightDto>()
|
||||
.Map(d => d.Id, s => s.FlightId);
|
||||
|
||||
config.NewConfig<UpdateFlightMongoCommand, FlightReadModel>()
|
||||
config.NewConfig<UpdateFlightMongo, FlightReadModel>()
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
|
||||
config.NewConfig<DeleteFlightMongoCommand, FlightReadModel>()
|
||||
config.NewConfig<DeleteFlightMongo, FlightReadModel>()
|
||||
.Map(d => d.FlightId, s => s.Id);
|
||||
|
||||
config.NewConfig<CreateFlightRequestDto, CreateFlightCommand>()
|
||||
.ConstructUsing(x => new CreateFlightCommand(x.FlightNumber, x.AircraftId, x.DepartureAirportId,
|
||||
config.NewConfig<CreateFlightRequestDto, CreateFlight>()
|
||||
.ConstructUsing(x => new CreateFlight(x.FlightNumber, x.AircraftId, x.DepartureAirportId,
|
||||
x.DepartureDate, x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.Price));
|
||||
|
||||
config.NewConfig<UpdateFlightRequestDto, UpdateFlightCommand>()
|
||||
.ConstructUsing(x => new UpdateFlightCommand(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate,
|
||||
config.NewConfig<UpdateFlightRequestDto, UpdateFlight>()
|
||||
.ConstructUsing(x => new UpdateFlight(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate,
|
||||
x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.IsDeleted, x.Price));
|
||||
|
||||
}
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Flights.Dtos;
|
||||
|
||||
namespace Flight.Flights.Features.GetAvailableFlights.Queries.V1;
|
||||
|
||||
public record GetAvailableFlightsQuery : IQuery<IEnumerable<FlightResponseDto>>, ICacheRequest
|
||||
{
|
||||
public string CacheKey => "GetAvailableFlightsQuery";
|
||||
public DateTime? AbsoluteExpirationRelativeToNow => DateTime.Now.AddHours(1);
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using MapsterMapper;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Flight.Flights.Features.GetAvailableFlights.Queries.V1;
|
||||
|
||||
public class GetAvailableFlightsQueryHandler : IQueryHandler<GetAvailableFlightsQuery, IEnumerable<FlightResponseDto>>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
|
||||
public GetAvailableFlightsQueryHandler(IMapper mapper, FlightReadDbContext flightReadDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FlightResponseDto>> Handle(GetAvailableFlightsQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(query, nameof(query));
|
||||
|
||||
var flight = (await _flightReadDbContext.Flight.AsQueryable().ToListAsync(cancellationToken))
|
||||
.Where(x => !x.IsDeleted);
|
||||
|
||||
if (!flight.Any())
|
||||
throw new FlightNotFountException();
|
||||
|
||||
return _mapper.Map<List<FlightResponseDto>>(flight);
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Flights.Features.GetAvailableFlights.Queries.V1;
|
||||
|
||||
public class GetAvailableFlightsQueryValidator : AbstractValidator<GetAvailableFlightsQuery>
|
||||
{
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Flights.Dtos;
|
||||
|
||||
namespace Flight.Flights.Features.GetFlightById.Queries.V1;
|
||||
|
||||
public record GetFlightByIdQuery(long Id) : IQuery<FlightResponseDto>;
|
||||
@ -1,38 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using MapsterMapper;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Flights.Features.GetFlightById.Queries.V1;
|
||||
|
||||
public class GetFlightByIdQueryHandler : IQueryHandler<GetFlightByIdQuery, FlightResponseDto>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
|
||||
public GetFlightByIdQueryHandler(IMapper mapper, FlightReadDbContext flightReadDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightResponseDto> Handle(GetFlightByIdQuery query, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(query, nameof(query));
|
||||
|
||||
var flight =
|
||||
await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == query.Id &&
|
||||
!x.IsDeleted, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
throw new FlightNotFountException();
|
||||
|
||||
return _mapper.Map<FlightResponseDto>(flight);
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Flights.Features.GetFlightById.Queries.V1;
|
||||
|
||||
public class GetFlightByIdQueryValidator : AbstractValidator<GetFlightByIdQuery>
|
||||
{
|
||||
public GetFlightByIdQueryValidator()
|
||||
{
|
||||
CascadeMode = CascadeMode.Stop;
|
||||
|
||||
RuleFor(x => x.Id).NotNull().WithMessage("Id is required!");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
namespace Flight.Flights.Features.GettingAvailableFlights.V1;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Data;
|
||||
using Dtos;
|
||||
using Exceptions;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using MongoDB.Driver;
|
||||
|
||||
public record GetAvailableFlights : IQuery<IEnumerable<FlightDto>>, ICacheRequest
|
||||
{
|
||||
public string CacheKey => "GetAvailableFlights";
|
||||
public DateTime? AbsoluteExpirationRelativeToNow => DateTime.Now.AddHours(1);
|
||||
}
|
||||
|
||||
internal class GetAvailableFlightsValidator : AbstractValidator<GetAvailableFlights>
|
||||
{
|
||||
}
|
||||
|
||||
internal class GetAvailableFlightsHandler : IQueryHandler<GetAvailableFlights, IEnumerable<FlightDto>>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
|
||||
public GetAvailableFlightsHandler(IMapper mapper, FlightReadDbContext flightReadDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FlightDto>> Handle(GetAvailableFlights request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flight = (await _flightReadDbContext.Flight.AsQueryable().ToListAsync(cancellationToken))
|
||||
.Where(x => !x.IsDeleted);
|
||||
|
||||
if (!flight.Any())
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
}
|
||||
|
||||
return _mapper.Map<IEnumerable<FlightDto>>(flight);
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,32 @@
|
||||
namespace Flight.Flights.Features.GettingAvailableFlights.V1;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.GetAvailableFlights.Queries.V1;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Flights.Features.GetAvailableFlights.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
|
||||
public class GetAvailableFlightsEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
|
||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("GetAvailableFlights")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status200OK,
|
||||
"GetAvailableFlights",
|
||||
typeof(IEnumerable<FlightResponseDto>)))
|
||||
typeof(IEnumerable<FlightDto>)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -41,12 +39,12 @@ public class GetAvailableFlightsEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> GetAvailableFlights(IMediator mediator, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await mediator.Send(new GetAvailableFlightsQuery(), cancellationToken);
|
||||
var result = await mediator.Send(new GetAvailableFlights(), cancellationToken);
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
namespace Flight.Flights.Features.GettingFlightById.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Data;
|
||||
using Dtos;
|
||||
using Exceptions;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
public record GetFlightById(long Id) : IQuery<FlightDto>;
|
||||
|
||||
internal class GetFlightByIdValidator : AbstractValidator<GetFlightById>
|
||||
{
|
||||
public GetFlightByIdValidator()
|
||||
{
|
||||
RuleFor(x => x.Id).NotNull().WithMessage("Id is required!");
|
||||
}
|
||||
}
|
||||
|
||||
internal class GetFlightByIdHandler : IQueryHandler<GetFlightById, FlightDto>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
|
||||
public GetFlightByIdHandler(IMapper mapper, FlightReadDbContext flightReadDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightReadDbContext = flightReadDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightDto> Handle(GetFlightById request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flight =
|
||||
await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == request.Id &&
|
||||
!x.IsDeleted, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
}
|
||||
|
||||
return _mapper.Map<FlightDto>(flight);
|
||||
}
|
||||
}
|
||||
@ -1,36 +1,34 @@
|
||||
namespace Flight.Flights.Features.GettingFlightById.V1;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.GetFlightById.Queries.V1;
|
||||
using Dtos;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Flights.Features.GetFlightById.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
|
||||
public class GetFlightByIdEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapGet($"{EndpointConfig.BaseApiPath}/flight/{{id}}", GetById)
|
||||
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/{{id}}", GetById)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("GetFlightById")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.Produces<FlightResponseDto>()
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.Produces<FlightDto>()
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status200OK,
|
||||
"GetFlightById",
|
||||
typeof(FlightResponseDto)))
|
||||
typeof(FlightDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -43,12 +41,12 @@ public class GetFlightByIdEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> GetById(long id, IMediator mediator, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await mediator.Send(new GetFlightByIdQuery(id), cancellationToken);
|
||||
var result = await mediator.Send(new GetFlightById(id), cancellationToken);
|
||||
|
||||
return Results.Ok(result);
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
|
||||
|
||||
public record UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Flight.Flights.Dtos;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Commands.V1;
|
||||
|
||||
public record UpdateFlightCommand(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, bool IsDeleted, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand, IInvalidateCacheRequest
|
||||
{
|
||||
public string CacheKey => "GetAvailableFlightsQuery";
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Commands.V1;
|
||||
|
||||
public class UpdateFlightCommandHandler : ICommandHandler<UpdateFlightCommand, FlightResponseDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UpdateFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightResponseDto> Handle(UpdateFlightCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == command.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
throw new FlightNotFountException();
|
||||
|
||||
|
||||
flight.Update(command.Id, command.FlightNumber, command.AircraftId, command.DepartureAirportId, command.DepartureDate,
|
||||
command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, command.Status, command.Price, command.IsDeleted);
|
||||
|
||||
var updateFlight = _flightDbContext.Flights.Update(flight);
|
||||
|
||||
return _mapper.Map<FlightResponseDto>(updateFlight.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
using Flight.Flights.Features.CreateFlight.Commands.V1;
|
||||
using FluentValidation;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Commands.V1;
|
||||
|
||||
public class UpdateFlightCommandValidator : AbstractValidator<CreateFlightCommand>
|
||||
{
|
||||
public UpdateFlightCommandValidator()
|
||||
{
|
||||
CascadeMode = CascadeMode.Stop;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Dtos;
|
||||
|
||||
public record UpdateFlightRequestDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId, DateTime DepartureDate, DateTime ArriveDate,
|
||||
long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted);
|
||||
@ -1,7 +1,8 @@
|
||||
namespace Flight.Flights.Features.UpdatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using BuildingBlocks.Core.Event;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Events.V1;
|
||||
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
|
||||
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;
|
||||
@ -0,0 +1,77 @@
|
||||
namespace Flight.Flights.Features.UpdatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Caching;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Exceptions;
|
||||
using Flight.Flights.Features.CreatingFlight.V1;
|
||||
using FluentValidation;
|
||||
using MapsterMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public record UpdateFlight(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, bool IsDeleted, decimal Price) : ICommand<FlightDto>, IInternalCommand, IInvalidateCacheRequest
|
||||
{
|
||||
public string CacheKey => "GetAvailableFlights";
|
||||
}
|
||||
|
||||
internal class UpdateFlightValidator : AbstractValidator<CreateFlight>
|
||||
{
|
||||
public UpdateFlightValidator()
|
||||
{
|
||||
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 UpdateFlightHandler : ICommandHandler<UpdateFlight, FlightDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UpdateFlightHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<FlightDto> Handle(UpdateFlight request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == request.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
}
|
||||
|
||||
|
||||
flight.Update(request.Id, request.FlightNumber, request.AircraftId, request.DepartureAirportId, request.DepartureDate,
|
||||
request.ArriveDate, request.ArriveAirportId, request.DurationMinutes, request.FlightDate, request.Status, request.Price, request.IsDeleted);
|
||||
|
||||
var updateFlight = _flightDbContext.Flights.Update(flight);
|
||||
|
||||
return _mapper.Map<FlightDto>(updateFlight.Entity);
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
namespace Flight.Flights.Features.UpdatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildingBlocks.Web;
|
||||
using Flight.Flights.Dtos;
|
||||
using Flight.Flights.Features.UpdateFlight.Commands.V1;
|
||||
using Flight.Flights.Features.UpdateFlight.Dtos;
|
||||
using Dtos;
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
@ -11,28 +13,27 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Endpoints.V1;
|
||||
|
||||
using Hellang.Middleware.ProblemDetails;
|
||||
public record UpdateFlightRequestDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId, DateTime DepartureDate, DateTime ArriveDate,
|
||||
long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted);
|
||||
|
||||
public class UpdateFlightEndpoint : IMinimalEndpoint
|
||||
{
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
|
||||
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
endpoints.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
|
||||
builder.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
|
||||
.RequireAuthorization()
|
||||
.WithTags("Flight")
|
||||
.WithName("UpdateFlight")
|
||||
.WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight"))
|
||||
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
|
||||
.Produces<FlightResponseDto>()
|
||||
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
|
||||
.Produces<FlightDto>()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status204NoContent,
|
||||
"Flight Updated",
|
||||
typeof(FlightResponseDto)))
|
||||
typeof(FlightDto)))
|
||||
.WithMetadata(
|
||||
new SwaggerResponseAttribute(
|
||||
StatusCodes.Status400BadRequest,
|
||||
@ -45,12 +46,12 @@ public class UpdateFlightEndpoint : IMinimalEndpoint
|
||||
typeof(StatusCodeProblemDetails)))
|
||||
.HasApiVersion(1.0);
|
||||
|
||||
return endpoints;
|
||||
return builder;
|
||||
}
|
||||
|
||||
private async Task<IResult> UpdateFlight(UpdateFlightRequestDto request, IMediator mediator, IMapper mapper, CancellationToken cancellationToken)
|
||||
{
|
||||
var command = mapper.Map<UpdateFlightCommand>(request);
|
||||
var command = mapper.Map<UpdateFlight>(request);
|
||||
|
||||
var result = await mediator.Send(command, cancellationToken);
|
||||
|
||||
@ -1,18 +1,25 @@
|
||||
using System.Threading;
|
||||
namespace Flight.Flights.Features.UpdatingFlight.V1;
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using Flight.Data;
|
||||
using Flight.Flights.Exceptions;
|
||||
using Flight.Flights.Models.Reads;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using Data;
|
||||
using Exceptions;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Models;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
|
||||
namespace Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
|
||||
public record UpdateFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
|
||||
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
|
||||
|
||||
public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongoCommand>
|
||||
|
||||
internal class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo>
|
||||
{
|
||||
private readonly FlightReadDbContext _flightReadDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
@ -25,17 +32,19 @@ public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<Unit> Handle(UpdateFlightMongoCommand command, CancellationToken cancellationToken)
|
||||
public async Task<Unit> Handle(UpdateFlightMongo request, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
Guard.Against.Null(request, nameof(request));
|
||||
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||
var flightReadModel = _mapper.Map<FlightReadModel>(request);
|
||||
|
||||
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
|
||||
|
||||
if (flight is null)
|
||||
{
|
||||
throw new FlightNotFountException();
|
||||
}
|
||||
|
||||
await _flightReadDbContext.Flight.UpdateOneAsync(
|
||||
x => x.FlightId == flightReadModel.FlightId,
|
||||
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using BuildingBlocks.Core.Model;
|
||||
using Flight.Flights.Features.CreateFlight.Events.Domain.V1;
|
||||
using Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
|
||||
using Flight.Flights.Features.UpdateFlight.Events.V1;
|
||||
|
||||
namespace Flight.Flights.Models;
|
||||
|
||||
using Features.CreatingFlight.V1;
|
||||
using Features.DeletingFlight.V1;
|
||||
using Features.UpdatingFlight.V1;
|
||||
|
||||
public record Flight : Aggregate<long>
|
||||
{
|
||||
public string FlightNumber { get; private set; }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
namespace Flight.Flights.Models;
|
||||
|
||||
namespace Flight.Flights.Models.Reads;
|
||||
using System;
|
||||
|
||||
public class FlightReadModel
|
||||
{
|
||||
@ -1,16 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using Flight.Flights.Features.GetFlightById;
|
||||
using Flight.Flights.Features.GetFlightById.Queries.V1;
|
||||
using Flight.Seats.Features.GetAvailableSeats;
|
||||
using Flight.Seats.Features.GetAvailableSeats.Queries.V1;
|
||||
using Flight.Seats.Features.ReserveSeat;
|
||||
using Flight.Seats.Features.ReserveSeat.Commands.V1;
|
||||
using Grpc.Core;
|
||||
using Mapster;
|
||||
using MediatR;
|
||||
|
||||
namespace Flight.GrpcServer.Services;
|
||||
|
||||
using Flights.Features.GettingFlightById.V1;
|
||||
using Seats.Features.GettingAvailableSeats.V1;
|
||||
using Seats.Features.ReservingSeat.Commands.V1;
|
||||
|
||||
public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
@ -22,13 +20,13 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
|
||||
|
||||
public override async Task<FlightResponse> GetById(GetByIdRequest request, ServerCallContext context)
|
||||
{
|
||||
var result = await _mediator.Send(new GetFlightByIdQuery(request.Id));
|
||||
var result = await _mediator.Send(new GetFlightById(request.Id));
|
||||
return result.Adapt<FlightResponse>();
|
||||
}
|
||||
|
||||
public override async Task<SeatsResponse> ReserveSeat(ReserveSeatRequest request, ServerCallContext context)
|
||||
{
|
||||
var result = await _mediator.Send(new ReserveSeatCommand(request.FlightId, request.SeatNumber));
|
||||
var result = await _mediator.Send(new ReserveSeat(request.FlightId, request.SeatNumber));
|
||||
return result.Adapt<SeatsResponse>();
|
||||
}
|
||||
|
||||
@ -36,7 +34,7 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
|
||||
{
|
||||
var result = new ListSeatsResponse();
|
||||
|
||||
var availableSeats = await _mediator.Send(new GetAvailableSeatsQuery(request.FlightId));
|
||||
var availableSeats = await _mediator.Send(new GetAvailableSeats(request.FlightId));
|
||||
|
||||
foreach (var availableSeat in availableSeats)
|
||||
{
|
||||
|
||||
3
src/Services/Flight/src/Flight/Seats/Dtos/SeatDto.cs
Normal file
3
src/Services/Flight/src/Flight/Seats/Dtos/SeatDto.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Flight.Seats.Dtos;
|
||||
|
||||
public record SeatDto(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId);
|
||||
@ -1,3 +0,0 @@
|
||||
namespace Flight.Seats.Dtos;
|
||||
|
||||
public record SeatResponseDto(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId);
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Flight.Seats.Exceptions;
|
||||
|
||||
namespace Flight.Seats.Features.GetAvailableSeats.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class AllSeatsFullException : BadRequestException
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Flight.Seats.Exceptions;
|
||||
|
||||
namespace Flight.Seats.Features.CreateSeat.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class SeatAlreadyExistException : ConflictException
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using BuildingBlocks.Exception;
|
||||
namespace Flight.Seats.Exceptions;
|
||||
|
||||
namespace Flight.Seats.Features.ReserveSeat.Exceptions;
|
||||
using BuildingBlocks.Exception;
|
||||
|
||||
public class SeatNumberIncorrectException : BadRequestException
|
||||
{
|
||||
@ -1,11 +0,0 @@
|
||||
using BuildingBlocks.Core.CQRS;
|
||||
using BuildingBlocks.Core.Event;
|
||||
using BuildingBlocks.IdsGenerator;
|
||||
using Flight.Seats.Dtos;
|
||||
|
||||
namespace Flight.Seats.Features.CreateSeat.Commands.V1;
|
||||
|
||||
public record CreateSeatCommand(string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId) : ICommand<SeatResponseDto>, IInternalCommand
|
||||
{
|
||||
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Ardalis.GuardClauses;
|
||||
using Flight.Data;
|
||||
using Flight.Seats.Dtos;
|
||||
using Flight.Seats.Features.CreateSeat.Exceptions;
|
||||
using Flight.Seats.Models;
|
||||
using MapsterMapper;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flight.Seats.Features.CreateSeat.Commands.V1;
|
||||
|
||||
public class CreateSeatCommandHandler : IRequestHandler<CreateSeatCommand, SeatResponseDto>
|
||||
{
|
||||
private readonly FlightDbContext _flightDbContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CreateSeatCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_flightDbContext = flightDbContext;
|
||||
}
|
||||
|
||||
public async Task<SeatResponseDto> Handle(CreateSeatCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
Guard.Against.Null(command, nameof(command));
|
||||
|
||||
var seat = await _flightDbContext.Seats.SingleOrDefaultAsync(x => x.Id == command.Id, cancellationToken);
|
||||
|
||||
if (seat is not null)
|
||||
throw new SeatAlreadyExistException();
|
||||
|
||||
var seatEntity = Seat.Create(command.Id, command.SeatNumber, command.Type, command.Class, command.FlightId);
|
||||
|
||||
var newSeat = await _flightDbContext.Seats.AddAsync(seatEntity, cancellationToken);
|
||||
|
||||
return _mapper.Map<SeatResponseDto>(newSeat.Entity);
|
||||
}
|
||||
}
|
||||
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