refactor: Refactor feature folder structure in vertical slice architecture

This commit is contained in:
Pc 2023-03-21 02:13:18 +03:30
parent 414ae0d305
commit 249b3015b5
199 changed files with 1425 additions and 1527 deletions

View File

@ -5,8 +5,8 @@
"elements": [ "elements": [
{ {
"type": "rectangle", "type": "rectangle",
"version": 240, "version": 242,
"versionNonce": 1959093945, "versionNonce": 1509780320,
"isDeleted": false, "isDeleted": false,
"id": "80OGzNPG6Gk8NAvbV3XaF", "id": "80OGzNPG6Gk8NAvbV3XaF",
"fillStyle": "solid", "fillStyle": "solid",
@ -18,7 +18,7 @@
"x": 648, "x": 648,
"y": 187, "y": 187,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#41a7f5", "backgroundColor": "#a8bffe",
"width": 538, "width": 538,
"height": 62, "height": 62,
"seed": 246982778, "seed": 246982778,
@ -32,14 +32,14 @@
"id": "46GLDhDwmnc8RGy3v8OK8" "id": "46GLDhDwmnc8RGy3v8OK8"
} }
], ],
"updated": 1678375014228, "updated": 1679316672934,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "text", "type": "text",
"version": 136, "version": 137,
"versionNonce": 833895767, "versionNonce": 703919968,
"isDeleted": false, "isDeleted": false,
"id": "46GLDhDwmnc8RGy3v8OK8", "id": "46GLDhDwmnc8RGy3v8OK8",
"fillStyle": "hachure", "fillStyle": "hachure",
@ -58,7 +58,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1678375014228, "updated": 1679315949309,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -71,8 +71,8 @@
}, },
{ {
"type": "rectangle", "type": "rectangle",
"version": 350, "version": 358,
"versionNonce": 435319161, "versionNonce": 356515488,
"isDeleted": false, "isDeleted": false,
"id": "nZuYK7wbLObwRvpRRLHay", "id": "nZuYK7wbLObwRvpRRLHay",
"fillStyle": "solid", "fillStyle": "solid",
@ -84,7 +84,7 @@
"x": 648, "x": 648,
"y": 263, "y": 263,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#ced4da", "backgroundColor": "#fea8d5",
"width": 538, "width": 538,
"height": 62, "height": 62,
"seed": 287502970, "seed": 287502970,
@ -98,14 +98,14 @@
"id": "OALII-DXtatRPgn_EkHfp" "id": "OALII-DXtatRPgn_EkHfp"
} }
], ],
"updated": 1678375286769, "updated": 1679316735759,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "text", "type": "text",
"version": 245, "version": 246,
"versionNonce": 63658455, "versionNonce": 1126108000,
"isDeleted": false, "isDeleted": false,
"id": "OALII-DXtatRPgn_EkHfp", "id": "OALII-DXtatRPgn_EkHfp",
"fillStyle": "hachure", "fillStyle": "hachure",
@ -124,7 +124,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1678375018983, "updated": 1679315949309,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -137,8 +137,8 @@
}, },
{ {
"type": "rectangle", "type": "rectangle",
"version": 276, "version": 282,
"versionNonce": 498715799, "versionNonce": 787808928,
"isDeleted": false, "isDeleted": false,
"id": "za_4vz64MSfPF5TWmD7wj", "id": "za_4vz64MSfPF5TWmD7wj",
"fillStyle": "solid", "fillStyle": "solid",
@ -150,7 +150,7 @@
"x": 650, "x": 650,
"y": 338, "y": 338,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#fa5252", "backgroundColor": "#f30358",
"width": 538, "width": 538,
"height": 62, "height": 62,
"seed": 676018342, "seed": 676018342,
@ -164,14 +164,14 @@
"id": "6CqYCSOKHqhqJ8nf4b-Sv" "id": "6CqYCSOKHqhqJ8nf4b-Sv"
} }
], ],
"updated": 1678375038578, "updated": 1679316783390,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "text", "type": "text",
"version": 188, "version": 189,
"versionNonce": 145052535, "versionNonce": 1441177440,
"isDeleted": false, "isDeleted": false,
"id": "6CqYCSOKHqhqJ8nf4b-Sv", "id": "6CqYCSOKHqhqJ8nf4b-Sv",
"fillStyle": "hachure", "fillStyle": "hachure",
@ -190,7 +190,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1678375033581, "updated": 1679315949309,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -203,8 +203,8 @@
}, },
{ {
"type": "rectangle", "type": "rectangle",
"version": 324, "version": 326,
"versionNonce": 1540569401, "versionNonce": 1669046112,
"isDeleted": false, "isDeleted": false,
"id": "t2sZwLLvmq3y2ndIbEomB", "id": "t2sZwLLvmq3y2ndIbEomB",
"fillStyle": "solid", "fillStyle": "solid",
@ -216,7 +216,7 @@
"x": 648, "x": 648,
"y": 413, "y": 413,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#07da63", "backgroundColor": "#9d9ca2",
"width": 538, "width": 538,
"height": 62, "height": 62,
"seed": 1173221990, "seed": 1173221990,
@ -230,14 +230,14 @@
"id": "b3wdaWjaVmgHpzMD26uKD" "id": "b3wdaWjaVmgHpzMD26uKD"
} }
], ],
"updated": 1678375048801, "updated": 1679316844215,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "text", "type": "text",
"version": 223, "version": 224,
"versionNonce": 1635337382, "versionNonce": 1385935712,
"isDeleted": false, "isDeleted": false,
"id": "b3wdaWjaVmgHpzMD26uKD", "id": "b3wdaWjaVmgHpzMD26uKD",
"fillStyle": "hachure", "fillStyle": "hachure",
@ -256,7 +256,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1677440080893, "updated": 1679315949310,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -269,20 +269,20 @@
}, },
{ {
"type": "rectangle", "type": "rectangle",
"version": 191, "version": 202,
"versionNonce": 502145367, "versionNonce": 1461187232,
"isDeleted": false, "isDeleted": false,
"id": "FQZImjU2-VUOATU9Yeyly", "id": "FQZImjU2-VUOATU9Yeyly",
"fillStyle": "solid", "fillStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"strokeStyle": "solid", "strokeStyle": "solid",
"roughness": 1, "roughness": 1,
"opacity": 90, "opacity": 100,
"angle": 0, "angle": 0,
"x": 678, "x": 678,
"y": 154, "y": 154,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#fff700 ", "backgroundColor": "#fefda8",
"width": 48, "width": 48,
"height": 361, "height": 361,
"seed": 1254939642, "seed": 1254939642,
@ -291,26 +291,26 @@
"type": 3 "type": 3
}, },
"boundElements": [], "boundElements": [],
"updated": 1678375337365, "updated": 1679316609154,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "rectangle", "type": "rectangle",
"version": 231, "version": 249,
"versionNonce": 579313209, "versionNonce": 1540775776,
"isDeleted": false, "isDeleted": false,
"id": "_Vw9EnXAyzxRDEzXCTfeL", "id": "_Vw9EnXAyzxRDEzXCTfeL",
"fillStyle": "solid", "fillStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"strokeStyle": "solid", "strokeStyle": "solid",
"roughness": 1, "roughness": 1,
"opacity": 90, "opacity": 100,
"angle": 0, "angle": 0,
"x": 743, "x": 742,
"y": 153.5, "y": 153.5,
"strokeColor": "#000000", "strokeColor": "#000000",
"backgroundColor": "#fff700 ", "backgroundColor": "#fefda8",
"width": 48, "width": 48,
"height": 361, "height": 361,
"seed": 523058342, "seed": 523058342,
@ -319,21 +319,21 @@
"type": 3 "type": 3
}, },
"boundElements": [], "boundElements": [],
"updated": 1678375355851, "updated": 1679316594766,
"link": null, "link": null,
"locked": false "locked": false
}, },
{ {
"type": "text", "type": "text",
"version": 245, "version": 249,
"versionNonce": 1220596518, "versionNonce": 871687840,
"isDeleted": false, "isDeleted": false,
"id": "hyJiOwPt7LFndn5R0xgfL", "id": "hyJiOwPt7LFndn5R0xgfL",
"fillStyle": "solid", "fillStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"strokeStyle": "solid", "strokeStyle": "solid",
"roughness": 1, "roughness": 1,
"opacity": 70, "opacity": 100,
"angle": 4.707547804955119, "angle": 4.707547804955119,
"x": 637.1248451774691, "x": 637.1248451774691,
"y": 317.9455509364301, "y": 317.9455509364301,
@ -345,7 +345,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1677440080893, "updated": 1679315961675,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -358,15 +358,15 @@
}, },
{ {
"type": "text", "type": "text",
"version": 179, "version": 182,
"versionNonce": 1053153850, "versionNonce": 1494113120,
"isDeleted": false, "isDeleted": false,
"id": "7KOHd5JA_wVMmwXPVT1N3", "id": "7KOHd5JA_wVMmwXPVT1N3",
"fillStyle": "solid", "fillStyle": "solid",
"strokeWidth": 1, "strokeWidth": 1,
"strokeStyle": "solid", "strokeStyle": "solid",
"roughness": 1, "roughness": 1,
"opacity": 70, "opacity": 80,
"angle": 4.7123889803846915, "angle": 4.7123889803846915,
"x": 695.6880416870117, "x": 695.6880416870117,
"y": 313.20000000000005, "y": 313.20000000000005,
@ -378,7 +378,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1677440080893, "updated": 1679315949310,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,
@ -391,8 +391,8 @@
}, },
{ {
"type": "text", "type": "text",
"version": 162, "version": 163,
"versionNonce": 1556577894, "versionNonce": 1243581088,
"isDeleted": false, "isDeleted": false,
"id": "SuFNrbzZGowiIybusnadN", "id": "SuFNrbzZGowiIybusnadN",
"fillStyle": "solid", "fillStyle": "solid",
@ -411,7 +411,7 @@
"groupIds": [], "groupIds": [],
"roundness": null, "roundness": null,
"boundElements": [], "boundElements": [],
"updated": 1677440080893, "updated": 1679315949310,
"link": null, "link": null,
"locked": false, "locked": false,
"fontSize": 28, "fontSize": 28,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -26,10 +26,14 @@
<Protobuf Include="GrpcClient\Protos\passenger.proto" GrpcServices="Both" /> <Protobuf Include="GrpcClient\Protos\passenger.proto" GrpcServices="Both" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="GrpcClient\Protos" /> <Folder Include="GrpcClient\Protos" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Integration.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Booking.Booking.Exceptions;
namespace Booking.Booking.Features.CreateBooking.Exceptions; using BuildingBlocks.Exception;
public class BookingAlreadyExistException : ConflictException public class BookingAlreadyExistException : ConflictException
{ {

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Booking.Booking.Exceptions;
namespace Booking.Booking.Features.CreateBooking.Exceptions; using BuildingBlocks.Exception;
public class FlightNotFoundException : NotFoundException public class FlightNotFoundException : NotFoundException
{ {

View File

@ -1,10 +1,10 @@
using Booking.Booking.Dtos; using Booking.Booking.Dtos;
using Booking.Booking.Features.CreateBooking.Commands.V1;
using Booking.Booking.Features.CreateBooking.Dtos.V1;
using Mapster; using Mapster;
namespace Booking.Booking.Features; namespace Booking.Booking.Features;
using CreatingBook.Commands.V1;
public class BookingMappings : IRegister public class BookingMappings : IRegister
{ {
public void Register(TypeAdapterConfig config) public void Register(TypeAdapterConfig config)
@ -17,7 +17,7 @@ public class BookingMappings : IRegister
x.Trip.Description)); x.Trip.Description));
config.NewConfig<CreateBookingRequestDto, CreateBookingCommand>() config.NewConfig<CreateBookingRequestDto, CreateBooking>()
.ConstructUsing(x => new CreateBookingCommand(x.PassengerId, x.FlightId, x.Description)); .ConstructUsing(x => new CreateBooking(x.PassengerId, x.FlightId, x.Description));
} }
} }

View File

@ -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();
}

View File

@ -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!");
}
}

View File

@ -1,3 +0,0 @@
namespace Booking.Booking.Features.CreateBooking.Dtos.V1;
public record CreateBookingRequestDto(long PassengerId, long FlightId, string Description);

View File

@ -1,7 +1,7 @@
using Booking.Booking.Models.ValueObjects; namespace Booking.Booking.Features.CreatingBook.Commands.V1;
using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Event;
using BuildingBlocks.Core.Model; using BuildingBlocks.Core.Model;
using Models.ValueObjects;
namespace Booking.Booking.Features.CreateBooking.Events.Domain.V1;
public record BookingCreatedDomainEvent(long Id, PassengerInfo PassengerInfo, Trip Trip) : Audit, IDomainEvent; public record BookingCreatedDomainEvent(long Id, PassengerInfo PassengerInfo, Trip Trip) : Audit, IDomainEvent;

View File

@ -1,16 +1,33 @@
namespace Booking.Booking.Features.CreatingBook.Commands.V1;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using Booking.Booking.Features.CreateBooking.Exceptions;
using Booking.Booking.Models.ValueObjects;
using BuildingBlocks.Core; using BuildingBlocks.Core;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using BuildingBlocks.EventStoreDB.Repository; using BuildingBlocks.EventStoreDB.Repository;
using BuildingBlocks.IdsGenerator;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Exceptions;
using Flight; using Flight;
using FluentValidation;
using Models.ValueObjects;
using Passenger; 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 IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
private readonly ICurrentUserProvider _currentUserProvider; private readonly ICurrentUserProvider _currentUserProvider;
@ -31,26 +48,30 @@ public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand,
_passengerGrpcServiceClient = passengerGrpcServiceClient; _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)); 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) if (flight is null)
{
throw new FlightNotFoundException(); throw new FlightNotFoundException();
}
var passenger = var passenger =
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest {Id = command.PassengerId}); await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest { Id = command.PassengerId });
var emptySeat = (await _flightGrpcServiceClient var emptySeat = (await _flightGrpcServiceClient
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest {FlightId = command.FlightId}).ResponseAsync) .GetAvailableSeatsAsync(new GetAvailableSeatsRequest { FlightId = command.FlightId }).ResponseAsync)
?.Items?.FirstOrDefault(); ?.Items?.FirstOrDefault();
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken); var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
if (reservation is not null && !reservation.IsDeleted) if (reservation is not null && !reservation.IsDeleted)
{
throw new BookingAlreadyExistException(); throw new BookingAlreadyExistException();
}
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip( var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId, flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,

View File

@ -1,29 +1,26 @@
using Booking.Booking.Features.CreateBooking.Commands.V1; namespace Booking.Booking.Features.CreatingBook.Commands.V1;
using Booking.Booking.Features.CreateBooking.Dtos.V1;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Hellang.Middleware.ProblemDetails;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Booking.Booking.Features.CreateBooking.Endpoints.V1; public record CreateBookingRequestDto(long PassengerId, long FlightId, string Description);
using Hellang.Middleware.ProblemDetails;
public class CreateBookingEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Booking") .WithTags("Booking")
.WithName("CreateBooking") .WithName("CreateBooking")
.WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking")) .WithMetadata(new SwaggerOperationAttribute("Create Booking", "Create Booking"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Booking").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Booking").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status200OK, StatusCodes.Status200OK,
@ -41,13 +38,13 @@ public class CreateBookingEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> CreateBooking(CreateBookingRequestDto request, IMediator mediator, IMapper mapper, private async Task<IResult> CreateBooking(CreateBookingRequestDto request, IMediator mediator, IMapper mapper,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var command = mapper.Map<CreateBookingCommand>(request); var command = mapper.Map<CreateBooking>(request);
var result = await mediator.Send(command, cancellationToken); var result = await mediator.Send(command, cancellationToken);

View File

@ -1,9 +1,10 @@
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
using Booking.Booking.Models.ValueObjects; using Booking.Booking.Models.ValueObjects;
using BuildingBlocks.EventStoreDB.Events; using BuildingBlocks.EventStoreDB.Events;
namespace Booking.Booking.Models; namespace Booking.Booking.Models;
using Features.CreatingBook.Commands.V1;
public record Booking : AggregateEventSourcing<long> public record Booking : AggregateEventSourcing<long>
{ {
public Trip Trip { get; private set; } public Trip Trip { get; private set; }

View File

@ -1,4 +1,3 @@
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
using Booking.Booking.Models.Reads; using Booking.Booking.Models.Reads;
using Booking.Data; using Booking.Data;
using BuildingBlocks.EventStoreDB.Events; using BuildingBlocks.EventStoreDB.Events;
@ -10,6 +9,8 @@ using MongoDB.Driver.Linq;
namespace Booking; namespace Booking;
using Booking.Features.CreatingBook.Commands.V1;
public class BookingProjection : IProjectionProcessor public class BookingProjection : IProjectionProcessor
{ {
private readonly BookingReadDbContext _bookingReadDbContext; private readonly BookingReadDbContext _bookingReadDbContext;

View File

@ -1,10 +1,11 @@
using Booking.Booking.Features.CreateBooking.Events.Domain.V1;
using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.Core; using BuildingBlocks.Core;
using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Event;
namespace Booking; namespace Booking;
using Booking.Features.CreatingBook.Commands.V1;
public sealed class EventMapper : IEventMapper public sealed class EventMapper : IEventMapper
{ {
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event) public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)

View File

@ -1,11 +1,11 @@
using AutoBogus; using AutoBogus;
using Booking.Booking.Features.CreateBooking;
using Booking.Booking.Features.CreateBooking.Commands.V1;
using BuildingBlocks.IdsGenerator; using BuildingBlocks.IdsGenerator;
namespace Integration.Test.Fakes; 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() public FakeCreateBookingCommand()
{ {

View File

@ -0,0 +1,3 @@
namespace Flight.Aircrafts.Dtos;
public record AircraftDto(long Id, string Name, string Model, int ManufacturingYear);

View File

@ -1,3 +0,0 @@
namespace Flight.Aircrafts.Dtos;
public record AircraftResponseDto(long Id, string Name, string Model, int ManufacturingYear);

View File

@ -1,8 +1,8 @@
namespace Flight.Aircrafts.Exceptions;
using System.Net; using System.Net;
using BuildingBlocks.Exception; using BuildingBlocks.Exception;
namespace Flight.Aircrafts.Features.CreateAircraft.Exceptions;
public class AircraftAlreadyExistException : AppException public class AircraftAlreadyExistException : AppException
{ {
public AircraftAlreadyExistException() : base("Aircraft already exist!", HttpStatusCode.Conflict) public AircraftAlreadyExistException() : base("Aircraft already exist!", HttpStatusCode.Conflict)

View File

@ -1,19 +1,16 @@
using BuildingBlocks.IdsGenerator; 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;
using Flight.Aircrafts.Models.Reads;
using Flight.Airports.Features.CreateAirport.Commands.V1;
using Mapster; using Mapster;
namespace Flight.Aircrafts.Features; namespace Flight.Aircrafts.Features;
using CreatingAircraft.V1;
public class AircraftMappings : IRegister public class AircraftMappings : IRegister
{ {
public void Register(TypeAdapterConfig config) public void Register(TypeAdapterConfig config)
{ {
config.NewConfig<CreateAircraftMongoCommand, AircraftReadModel>() config.NewConfig<CreateAircraftMongo, AircraftReadModel>()
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId()) .Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.AircraftId, s => s.Id); .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.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.AircraftId, s => s.Id); .Map(d => d.AircraftId, s => s.Id);
config.NewConfig<CreateAircraftRequestDto, CreateAircraftCommand>() config.NewConfig<CreateAircraftRequestDto, CreatingAircraft.V1.CreateAircraft>()
.ConstructUsing(x => new CreateAircraftCommand(x.Name, x.Model, x.ManufacturingYear)); .ConstructUsing(x => new CreatingAircraft.V1.CreateAircraft(x.Name, x.Model, x.ManufacturingYear));
} }
} }

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -1,4 +0,0 @@
namespace Flight.Aircrafts.Features.CreateAircraft.Dtos.V1;
public record CreateAircraftRequestDto(string Name, string Model, int ManufacturingYear);

View File

@ -1,5 +1,5 @@
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using BuildingBlocks.Core.Event; 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; public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent;

View File

@ -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);
}
}

View File

@ -1,9 +1,10 @@
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Aircrafts.Dtos; using Dtos;
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1; using Hellang.Middleware.ProblemDetails;
using Flight.Aircrafts.Features.CreateAircraft.Dtos.V1;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -11,25 +12,23 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Aircrafts.Features.CreateAircraft.Endpoints.V1; public record CreateAircraftRequestDto(string Name, string Model, int ManufacturingYear);
using Hellang.Middleware.ProblemDetails;
public class CreateAircraftEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("CreateAircraft") .WithName("CreateAircraft")
.WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft")) .WithMetadata(new SwaggerOperationAttribute("Create Aircraft", "Create Aircraft"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status200OK, StatusCodes.Status200OK,
"Aircraft Created", "Aircraft Created",
typeof(AircraftResponseDto))) typeof(AircraftDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -42,13 +41,13 @@ public class CreateAircraftEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> CreateAircraft(CreateAircraftRequestDto request, IMediator mediator, IMapper mapper, private async Task<IResult> CreateAircraft(CreateAircraftRequestDto request, IMediator mediator, IMapper mapper,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var command = mapper.Map<CreateAircraftCommand>(request); var command = mapper.Map<CreateAircraft>(request);
var result = await mediator.Send(command, cancellationToken); var result = await mediator.Send(command, cancellationToken);

View File

@ -1,23 +1,26 @@
using System.Threading; namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using Flight.Aircrafts.Features.CreateAircraft.Exceptions; using BuildingBlocks.Core.Event;
using Flight.Aircrafts.Models.Reads; using Exceptions;
using Flight.Aircrafts.Models;
using Flight.Data; using Flight.Data;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; 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 FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
public CreateAircraftMongoCommandHandler( public CreateAircraftMongoHandler(
FlightReadDbContext flightReadDbContext, FlightReadDbContext flightReadDbContext,
IMapper mapper) IMapper mapper)
{ {
@ -25,17 +28,19 @@ public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftM
_mapper = mapper; _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() var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
.FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken); .FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken);
if (aircraft is not null) if (aircraft is not null)
{
throw new AircraftAlreadyExistException(); throw new AircraftAlreadyExistException();
}
await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken); await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken);

View File

@ -1,8 +1,9 @@
using BuildingBlocks.Core.Model; using BuildingBlocks.Core.Model;
using Flight.Aircrafts.Features.CreateAircraft.Events.Domain.V1;
namespace Flight.Aircrafts.Models; namespace Flight.Aircrafts.Models;
using Features.CreatingAircraft.V1;
public record Aircraft : Aggregate<long> public record Aircraft : Aggregate<long>
{ {
public string Name { get; private set; } public string Name { get; private set; }

View File

@ -1,4 +1,4 @@
namespace Flight.Aircrafts.Models.Reads; namespace Flight.Aircrafts.Models;
public class AircraftReadModel public class AircraftReadModel
{ {

View File

@ -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));
}
}

View File

@ -0,0 +1,3 @@
namespace Flight.Airports.Dtos;
public record AirportDto(long Id, string Name, string Address, string Code);

View File

@ -1,3 +0,0 @@
namespace Flight.Airports.Dtos;
public record AirportResponseDto(long Id, string Name, string Address, string Code);

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Flight.Airports.Exceptions;
namespace Flight.Airports.Features.CreateAirport.Exceptions; using BuildingBlocks.Exception;
public class AirportAlreadyExistException : ConflictException public class AirportAlreadyExistException : ConflictException
{ {

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -1,3 +0,0 @@
namespace Flight.Airports.Features.CreateAirport.Dtos.V1;
public record CreateAirportRequestDto(string Name, string Address, string Code);

View File

@ -1,5 +1,5 @@
namespace Flight.Airports.Features.CreatingAirport.V1;
using BuildingBlocks.Core.Event; 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; public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code, bool IsDeleted) : IDomainEvent;

View File

@ -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);
}
}

View File

@ -1,9 +1,10 @@
namespace Flight.Airports.Features.CreatingAirport.V1;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Airports.Dtos; using Dtos;
using Flight.Airports.Features.CreateAirport.Commands.V1; using Hellang.Middleware.ProblemDetails;
using Flight.Airports.Features.CreateAirport.Dtos.V1;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -11,25 +12,23 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Airports.Features.CreateAirport.Endpoints.V1; public record CreateAirportRequestDto(string Name, string Address, string Code);
using Hellang.Middleware.ProblemDetails;
public class CreateAirportEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("CreateAirport") .WithName("CreateAirport")
.WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport")) .WithMetadata(new SwaggerOperationAttribute("Create Airport", "Create Airport"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status200OK, StatusCodes.Status200OK,
"Airport Created", "Airport Created",
typeof(AirportResponseDto))) typeof(AirportDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -42,13 +41,13 @@ public class CreateAirportEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> CreateAirport(CreateAirportRequestDto request, IMediator mediator, IMapper mapper, private async Task<IResult> CreateAirport(CreateAirportRequestDto request, IMediator mediator, IMapper mapper,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var command = mapper.Map<CreateAirportCommand>(request); var command = mapper.Map<CreateAirport>(request);
var result = await mediator.Send(command, cancellationToken); var result = await mediator.Send(command, cancellationToken);

View File

@ -1,23 +1,26 @@
using System.Threading; namespace Flight.Airports.Features.CreatingAirport.V1;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using Flight.Airports.Features.CreateAirport.Exceptions; using BuildingBlocks.Core.Event;
using Flight.Airports.Models.Reads; using Exceptions;
using Flight.Data; using Models;
using Data;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; 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 FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
public CreateAirportMongoCommandHandler( public CreateAirportMongoHandler(
FlightReadDbContext flightReadDbContext, FlightReadDbContext flightReadDbContext,
IMapper mapper) IMapper mapper)
{ {
@ -25,17 +28,19 @@ public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMon
_mapper = mapper; _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() var aircraft = await _flightReadDbContext.Airport.AsQueryable()
.FirstOrDefaultAsync(x => x.AirportId == airportReadModel.AirportId, cancellationToken); .FirstOrDefaultAsync(x => x.AirportId == airportReadModel.AirportId, cancellationToken);
if (aircraft is not null) if (aircraft is not null)
{
throw new AirportAlreadyExistException(); throw new AirportAlreadyExistException();
}
await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken); await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken);

View File

@ -1,9 +1,10 @@
using BuildingBlocks.Core.Model; using BuildingBlocks.Core.Model;
using BuildingBlocks.IdsGenerator; using BuildingBlocks.IdsGenerator;
using Flight.Airports.Features.CreateAirport.Events.Domain.V1;
namespace Flight.Airports.Models; namespace Flight.Airports.Models;
using Features.CreatingAirport.V1;
public record Airport : Aggregate<long> public record Airport : Aggregate<long>
{ {
public string Name { get; private set; } public string Name { get; private set; }

View File

@ -1,4 +1,4 @@
namespace Flight.Airports.Models.Reads; namespace Flight.Airports.Models;
public class AirportReadModel public class AirportReadModel
{ {

View File

@ -1,14 +1,15 @@
using BuildingBlocks.Mongo; 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 Humanizer;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MongoDB.Driver; using MongoDB.Driver;
namespace Flight.Data; namespace Flight.Data;
using Aircrafts.Models;
using Airports.Models;
using Flights.Models;
using Seats.Models;
public class FlightReadDbContext : MongoDbContext public class FlightReadDbContext : MongoDbContext
{ {
public FlightReadDbContext(IOptions<MongoOptions> options) : base(options) public FlightReadDbContext(IOptions<MongoOptions> options) : base(options)

View File

@ -3,12 +3,8 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.EFCore; using BuildingBlocks.EFCore;
using Flight.Aircrafts.Models; using Flight.Aircrafts.Models;
using Flight.Aircrafts.Models.Reads;
using Flight.Airports.Models; using Flight.Airports.Models;
using Flight.Airports.Models.Reads;
using Flight.Flights.Models.Reads;
using Flight.Seats.Models; using Flight.Seats.Models;
using Flight.Seats.Models.Reads;
using MapsterMapper; using MapsterMapper;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MongoDB.Driver; using MongoDB.Driver;
@ -16,6 +12,8 @@ using MongoDB.Driver.Linq;
namespace Flight.Data.Seed; namespace Flight.Data.Seed;
using Flights.Models;
public class FlightDataSeeder : IDataSeeder public class FlightDataSeeder : IDataSeeder
{ {
private readonly FlightDbContext _flightDbContext; private readonly FlightDbContext _flightDbContext;

View File

@ -1,23 +1,17 @@
using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.Core; using BuildingBlocks.Core;
using BuildingBlocks.Core.Event; 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; 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/ // ref: https://www.ledjonbehluli.com/posts/domain_to_integration_event/
public sealed class EventMapper : IEventMapper public sealed class EventMapper : IEventMapper
{ {
@ -40,16 +34,16 @@ public sealed class EventMapper : IEventMapper
{ {
return @event switch 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), 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), 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), 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), AircraftCreatedDomainEvent e => new CreateAircraftMongo(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
AirportCreatedDomainEvent e => new CreateAirportMongoCommand(e.Id, e.Name, e.Address, e.Code, e.IsDeleted), AirportCreatedDomainEvent e => new CreateAirportMongo(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), SeatCreatedDomainEvent e => new CreateSeatMongo(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), SeatReservedDomainEvent e => new ReserveSeatMongo(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
_ => null _ => null
}; };
} }

View File

@ -19,7 +19,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="Data\Migrations" /> <Folder Include="Data\Migrations" />
<Folder Include="Seats\Features\CreateSeat\Events\Domain" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -30,4 +29,20 @@
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" /> <ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
</ItemGroup> </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> </Project>

View File

@ -3,6 +3,6 @@ using Flight.Flights.Models;
namespace Flight.Flights.Dtos; 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, DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price); Enums.FlightStatus Status, decimal Price);

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Flight.Flights.Exceptions;
namespace Flight.Flights.Features.CreateFlight.Exceptions; using BuildingBlocks.Exception;
public class FlightAlreadyExistException : ConflictException public class FlightAlreadyExistException : ConflictException
{ {

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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);
}
}

View File

@ -1,10 +1,11 @@
namespace Flight.Flights.Features.CreatingFlight.V1;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Flights.Dtos; using Flight.Flights.Dtos;
using Flight.Flights.Features.CreateFlight.Commands.V1; using Hellang.Middleware.ProblemDetails;
using Flight.Flights.Features.CreateFlight.Dtos.V1;
using Mapster;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -12,25 +13,25 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.CreateFlight.Endpoints.V1; public record CreateFlightRequestDto(string FlightNumber, long AircraftId, long DepartureAirportId,
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
using Hellang.Middleware.ProblemDetails; decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price);
public class CreateFlightEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("CreateFlight") .WithName("CreateFlight")
.WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight")) .WithMetadata(new SwaggerOperationAttribute("Create Flight", "Create Flight"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status201Created, StatusCodes.Status201Created,
"Flight Created", "Flight Created",
typeof(FlightResponseDto))) typeof(FlightDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -43,13 +44,13 @@ public class CreateFlightEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> CreateFlight(CreateFlightRequestDto request, IMediator mediator, IMapper mapper, private async Task<IResult> CreateFlight(CreateFlightRequestDto request, IMediator mediator, IMapper mapper,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var command = mapper.Map<CreateFlightCommand>(request); var command = mapper.Map<CreateFlight>(request);
var result = await mediator.Send(command, cancellationToken); var result = await mediator.Send(command, cancellationToken);

View File

@ -1,24 +1,29 @@
using System.Threading; namespace Flight.Flights.Features.CreatingFlight.V1;
using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using Flight.Data; using Flight.Data;
using Flight.Flights.Exceptions; using Flight.Flights.Exceptions;
using Flight.Flights.Features.CreateFlight.Exceptions;
using Flight.Flights.Models.Reads;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Models;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; 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 FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
public CreateFlightMongoCommandHandler( public CreateFlightMongoHandler(
FlightReadDbContext flightReadDbContext, FlightReadDbContext flightReadDbContext,
IMapper mapper) IMapper mapper)
{ {
@ -26,17 +31,19 @@ public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongo
_mapper = mapper; _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() var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken); .FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is not null) if (flight is not null)
{
throw new FlightAlreadyExistException(); throw new FlightAlreadyExistException();
}
await _flightReadDbContext.Flight.InsertOneAsync(flightReadModel, cancellationToken: cancellationToken); await _flightReadDbContext.Flight.InsertOneAsync(flightReadModel, cancellationToken: cancellationToken);

View File

@ -1,8 +1,8 @@
namespace Flight.Flights.Features.CreatingFlight.V1;
using System; using System;
using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Event;
namespace Flight.Flights.Features.CreateFlight.Events.Domain.V1;
public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, public record FlightCreatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent; DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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;

View File

@ -1,35 +1,48 @@
using System.Threading; namespace Flight.Flights.Features.DeletingFlight.V1;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using Flight.Data; using BuildingBlocks.Core.Event;
using Flight.Flights.Dtos; using Data;
using Flight.Flights.Exceptions; using Dtos;
using Exceptions;
using FluentValidation;
using MapsterMapper; using MapsterMapper;
using Microsoft.EntityFrameworkCore; 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 FlightDbContext _flightDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
public DeleteFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext) public DeleteFlightHandler(IMapper mapper, FlightDbContext flightDbContext)
{ {
_mapper = mapper; _mapper = mapper;
_flightDbContext = flightDbContext; _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) if (flight is null)
{
throw new FlightNotFountException(); throw new FlightNotFountException();
}
var deleteFlight = _flightDbContext.Flights.Remove(flight).Entity; 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.DepartureDate, deleteFlight.ArriveDate, deleteFlight.ArriveAirportId, deleteFlight.DurationMinutes,
deleteFlight.FlightDate, deleteFlight.Status, deleteFlight.Price); deleteFlight.FlightDate, deleteFlight.Status, deleteFlight.Price);
return _mapper.Map<FlightResponseDto>(deleteFlight); return _mapper.Map<FlightDto>(deleteFlight);
} }
} }

View File

@ -1,33 +1,31 @@
using System.Threading; namespace Flight.Flights.Features.DeletingFlight.V1;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Flights.Dtos; using Flight.Flights.Dtos;
using Flight.Flights.Features.DeleteFlight.Commands.V1; using Hellang.Middleware.ProblemDetails;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.DeleteFlight.Endpoints.V1;
using Hellang.Middleware.ProblemDetails;
public class DeleteFlightEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("DeleteFlight") .WithName("DeleteFlight")
.WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight")) .WithMetadata(new SwaggerOperationAttribute("Delete Flight", "Delete Flight"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status204NoContent, StatusCodes.Status204NoContent,
"Flight Deleted", "Flight Deleted",
typeof(FlightResponseDto))) typeof(FlightDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -40,12 +38,12 @@ public class DeleteFlightEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> DeleteFlight(long id, IMediator mediator, CancellationToken cancellationToken) 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(); return Results.NoContent();
} }

View File

@ -1,18 +1,24 @@
using System.Threading; namespace Flight.Flights.Features.DeletingFlight.V1;
using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using Flight.Data; using BuildingBlocks.Core.Event;
using Flight.Flights.Exceptions; using Data;
using Flight.Flights.Models.Reads; using Exceptions;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Models;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; 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 FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
@ -25,17 +31,19 @@ public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongo
_mapper = mapper; _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() var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken); .FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is null) if (flight is null)
{
throw new FlightNotFountException(); throw new FlightNotFountException();
}
await _flightReadDbContext.Flight.UpdateOneAsync( await _flightReadDbContext.Flight.UpdateOneAsync(
x => x.FlightId == flightReadModel.FlightId, x => x.FlightId == flightReadModel.FlightId,

View File

@ -1,7 +1,7 @@
using System; namespace Flight.Flights.Features.DeletingFlight.V1;
using BuildingBlocks.Core.Event;
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, public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,

View File

@ -1,27 +1,23 @@
using AutoMapper;
using BuildingBlocks.IdsGenerator; using BuildingBlocks.IdsGenerator;
using Flight.Flights.Dtos; 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; using Mapster;
namespace Flight.Flights.Features; namespace Flight.Flights.Features;
using CreatingFlight.V1;
using DeletingFlight.V1;
using Models;
using UpdatingFlight.V1;
public class FlightMappings : IRegister public class FlightMappings : IRegister
{ {
public void Register(TypeAdapterConfig config) public void Register(TypeAdapterConfig config)
{ {
config.NewConfig<Models.Flight, FlightResponseDto>() config.NewConfig<Models.Flight, FlightDto>()
.ConstructUsing(x => new FlightResponseDto(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate, .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)); 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.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.FlightId, s => s.Id); .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.Id, s => SnowFlakIdGenerator.NewId())
.Map(d => d.FlightId, s => s.Id); .Map(d => d.FlightId, s => s.Id);
config.NewConfig<FlightReadModel, FlightResponseDto>() config.NewConfig<FlightReadModel, FlightDto>()
.Map(d => d.Id, s => s.FlightId); .Map(d => d.Id, s => s.FlightId);
config.NewConfig<UpdateFlightMongoCommand, FlightReadModel>() config.NewConfig<UpdateFlightMongo, FlightReadModel>()
.Map(d => d.FlightId, s => s.Id); .Map(d => d.FlightId, s => s.Id);
config.NewConfig<DeleteFlightMongoCommand, FlightReadModel>() config.NewConfig<DeleteFlightMongo, FlightReadModel>()
.Map(d => d.FlightId, s => s.Id); .Map(d => d.FlightId, s => s.Id);
config.NewConfig<CreateFlightRequestDto, CreateFlightCommand>() config.NewConfig<CreateFlightRequestDto, CreateFlight>()
.ConstructUsing(x => new CreateFlightCommand(x.FlightNumber, x.AircraftId, x.DepartureAirportId, .ConstructUsing(x => new CreateFlight(x.FlightNumber, x.AircraftId, x.DepartureAirportId,
x.DepartureDate, x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.Price)); x.DepartureDate, x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.Price));
config.NewConfig<UpdateFlightRequestDto, UpdateFlightCommand>() config.NewConfig<UpdateFlightRequestDto, UpdateFlight>()
.ConstructUsing(x => new UpdateFlightCommand(x.Id, x.FlightNumber, x.AircraftId, x.DepartureAirportId, x.DepartureDate, .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)); x.ArriveDate, x.ArriveAirportId, x.DurationMinutes, x.FlightDate, x.Status, x.IsDeleted, x.Price));
} }

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -1,7 +0,0 @@
using FluentValidation;
namespace Flight.Flights.Features.GetAvailableFlights.Queries.V1;
public class GetAvailableFlightsQueryValidator : AbstractValidator<GetAvailableFlightsQuery>
{
}

View File

@ -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>;

View File

@ -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);
}
}

View File

@ -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!");
}
}

View File

@ -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);
}
}

View File

@ -1,34 +1,32 @@
namespace Flight.Flights.Features.GettingAvailableFlights.V1;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Flights.Dtos; using Flight.Flights.Dtos;
using Flight.Flights.Features.GetAvailableFlights.Queries.V1; using Hellang.Middleware.ProblemDetails;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.GetAvailableFlights.Endpoints.V1;
using Hellang.Middleware.ProblemDetails;
public class GetAvailableFlightsEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("GetAvailableFlights") .WithName("GetAvailableFlights")
.WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights")) .WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status200OK, StatusCodes.Status200OK,
"GetAvailableFlights", "GetAvailableFlights",
typeof(IEnumerable<FlightResponseDto>))) typeof(IEnumerable<FlightDto>)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -41,12 +39,12 @@ public class GetAvailableFlightsEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> GetAvailableFlights(IMediator mediator, CancellationToken cancellationToken) 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); return Results.Ok(result);
} }

View File

@ -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);
}
}

View File

@ -1,36 +1,34 @@
namespace Flight.Flights.Features.GettingFlightById.V1;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Flights.Dtos; using Dtos;
using Flight.Flights.Features.GetFlightById.Queries.V1; using Hellang.Middleware.ProblemDetails;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.GetFlightById.Endpoints.V1;
using Hellang.Middleware.ProblemDetails;
public class GetFlightByIdEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("GetFlightById") .WithName("GetFlightById")
.WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id")) .WithMetadata(new SwaggerOperationAttribute("Get Flight By Id", "Get Flight By Id"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.Produces<FlightResponseDto>() .Produces<FlightDto>()
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status400BadRequest)
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status200OK, StatusCodes.Status200OK,
"GetFlightById", "GetFlightById",
typeof(FlightResponseDto))) typeof(FlightDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -43,12 +41,12 @@ public class GetFlightByIdEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> GetById(long id, IMediator mediator, CancellationToken cancellationToken) 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); return Results.Ok(result);
} }

View File

@ -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;

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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);

View File

@ -1,7 +1,8 @@
namespace Flight.Flights.Features.UpdatingFlight.V1;
using System; using System;
using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Event;
namespace Flight.Flights.Features.UpdateFlight.Events.V1;
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent; DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;

View File

@ -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);
}
}

View File

@ -1,9 +1,11 @@
namespace Flight.Flights.Features.UpdatingFlight.V1;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BuildingBlocks.Web; using BuildingBlocks.Web;
using Flight.Flights.Dtos; using Dtos;
using Flight.Flights.Features.UpdateFlight.Commands.V1; using Hellang.Middleware.ProblemDetails;
using Flight.Flights.Features.UpdateFlight.Dtos;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -11,28 +13,27 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.UpdateFlight.Endpoints.V1; 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);
using Hellang.Middleware.ProblemDetails;
public class UpdateFlightEndpoint : IMinimalEndpoint 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() .RequireAuthorization()
.WithTags("Flight") .WithTags("Flight")
.WithName("UpdateFlight") .WithName("UpdateFlight")
.WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight")) .WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build()) .WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.Produces<FlightResponseDto>() .Produces<FlightDto>()
.Produces(StatusCodes.Status204NoContent) .Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status400BadRequest)
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status204NoContent, StatusCodes.Status204NoContent,
"Flight Updated", "Flight Updated",
typeof(FlightResponseDto))) typeof(FlightDto)))
.WithMetadata( .WithMetadata(
new SwaggerResponseAttribute( new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest, StatusCodes.Status400BadRequest,
@ -45,12 +46,12 @@ public class UpdateFlightEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails))) typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0); .HasApiVersion(1.0);
return endpoints; return builder;
} }
private async Task<IResult> UpdateFlight(UpdateFlightRequestDto request, IMediator mediator, IMapper mapper, CancellationToken cancellationToken) 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); var result = await mediator.Send(command, cancellationToken);

View File

@ -1,18 +1,25 @@
using System.Threading; namespace Flight.Flights.Features.UpdatingFlight.V1;
using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ardalis.GuardClauses; using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.CQRS;
using Flight.Data; using BuildingBlocks.Core.Event;
using Flight.Flights.Exceptions; using Data;
using Flight.Flights.Models.Reads; using Exceptions;
using MapsterMapper; using MapsterMapper;
using MediatR; using MediatR;
using Models;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.Linq; 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 FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper; private readonly IMapper _mapper;
@ -25,17 +32,19 @@ public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo
_mapper = mapper; _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() var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken); .FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is null) if (flight is null)
{
throw new FlightNotFountException(); throw new FlightNotFountException();
}
await _flightReadDbContext.Flight.UpdateOneAsync( await _flightReadDbContext.Flight.UpdateOneAsync(
x => x.FlightId == flightReadModel.FlightId, x => x.FlightId == flightReadModel.FlightId,

View File

@ -1,11 +1,12 @@
using System; using System;
using BuildingBlocks.Core.Model; 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; namespace Flight.Flights.Models;
using Features.CreatingFlight.V1;
using Features.DeletingFlight.V1;
using Features.UpdatingFlight.V1;
public record Flight : Aggregate<long> public record Flight : Aggregate<long>
{ {
public string FlightNumber { get; private set; } public string FlightNumber { get; private set; }

View File

@ -1,6 +1,6 @@
using System; namespace Flight.Flights.Models;
namespace Flight.Flights.Models.Reads; using System;
public class FlightReadModel public class FlightReadModel
{ {

View File

@ -1,16 +1,14 @@
using System.Threading.Tasks; 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 Grpc.Core;
using Mapster; using Mapster;
using MediatR; using MediatR;
namespace Flight.GrpcServer.Services; 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 public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
@ -22,13 +20,13 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
public override async Task<FlightResponse> GetById(GetByIdRequest request, ServerCallContext context) 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>(); return result.Adapt<FlightResponse>();
} }
public override async Task<SeatsResponse> ReserveSeat(ReserveSeatRequest request, ServerCallContext context) 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>(); return result.Adapt<SeatsResponse>();
} }
@ -36,7 +34,7 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
{ {
var result = new ListSeatsResponse(); 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) foreach (var availableSeat in availableSeats)
{ {

View File

@ -0,0 +1,3 @@
namespace Flight.Seats.Dtos;
public record SeatDto(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId);

View File

@ -1,3 +0,0 @@
namespace Flight.Seats.Dtos;
public record SeatResponseDto(long Id, string SeatNumber, Enums.SeatType Type, Enums.SeatClass Class, long FlightId);

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Flight.Seats.Exceptions;
namespace Flight.Seats.Features.GetAvailableSeats.Exceptions; using BuildingBlocks.Exception;
public class AllSeatsFullException : BadRequestException public class AllSeatsFullException : BadRequestException
{ {

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Flight.Seats.Exceptions;
namespace Flight.Seats.Features.CreateSeat.Exceptions; using BuildingBlocks.Exception;
public class SeatAlreadyExistException : ConflictException public class SeatAlreadyExistException : ConflictException
{ {

View File

@ -1,6 +1,6 @@
using BuildingBlocks.Exception; namespace Flight.Seats.Exceptions;
namespace Flight.Seats.Features.ReserveSeat.Exceptions; using BuildingBlocks.Exception;
public class SeatNumberIncorrectException : BadRequestException public class SeatNumberIncorrectException : BadRequestException
{ {

View File

@ -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();
}

View File

@ -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