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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

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

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
{

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
{

View File

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

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.Model;
namespace Booking.Booking.Features.CreateBooking.Events.Domain.V1;
using Models.ValueObjects;
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 Booking.Booking.Features.CreateBooking.Exceptions;
using Booking.Booking.Models.ValueObjects;
using BuildingBlocks.Core;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using BuildingBlocks.EventStoreDB.Repository;
using BuildingBlocks.IdsGenerator;
using BuildingBlocks.Web;
using Exceptions;
using Flight;
using FluentValidation;
using Models.ValueObjects;
using Passenger;
namespace Booking.Booking.Features.CreateBooking.Commands.V1;
public record CreateBooking(long PassengerId, long FlightId, string Description) : ICommand<ulong>, IInternalCommand
{
public long Id { get; init; } = SnowFlakIdGenerator.NewId();
}
public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand, ulong>
internal class CreateBookingValidator : AbstractValidator<CreateBooking>
{
public CreateBookingValidator()
{
RuleFor(x => x.FlightId).NotNull().WithMessage("FlightId is required!");
RuleFor(x => x.PassengerId).NotNull().WithMessage("PassengerId is required!");
}
}
internal class CreateBookingCommandHandler : ICommandHandler<CreateBooking, ulong>
{
private readonly IEventStoreDBRepository<Models.Booking> _eventStoreDbRepository;
private readonly ICurrentUserProvider _currentUserProvider;
@ -31,26 +48,30 @@ public class CreateBookingCommandHandler : ICommandHandler<CreateBookingCommand,
_passengerGrpcServiceClient = passengerGrpcServiceClient;
}
public async Task<ulong> Handle(CreateBookingCommand command, CancellationToken cancellationToken)
public async Task<ulong> Handle(CreateBooking command, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
var flight = await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest {Id = command.FlightId});
var flight = await _flightGrpcServiceClient.GetByIdAsync(new Flight.GetByIdRequest { Id = command.FlightId });
if (flight is null)
{
throw new FlightNotFoundException();
}
var passenger =
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest {Id = command.PassengerId});
await _passengerGrpcServiceClient.GetByIdAsync(new Passenger.GetByIdRequest { Id = command.PassengerId });
var emptySeat = (await _flightGrpcServiceClient
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest {FlightId = command.FlightId}).ResponseAsync)
.GetAvailableSeatsAsync(new GetAvailableSeatsRequest { FlightId = command.FlightId }).ResponseAsync)
?.Items?.FirstOrDefault();
var reservation = await _eventStoreDbRepository.Find(command.Id, cancellationToken);
if (reservation is not null && !reservation.IsDeleted)
{
throw new BookingAlreadyExistException();
}
var aggrigate = Models.Booking.Create(command.Id, new PassengerInfo(passenger.Name), new Trip(
flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
using AutoBogus;
using Booking.Booking.Features.CreateBooking;
using Booking.Booking.Features.CreateBooking.Commands.V1;
using BuildingBlocks.IdsGenerator;
namespace Integration.Test.Fakes;
public sealed class FakeCreateBookingCommand : AutoFaker<CreateBookingCommand>
using global::Booking.Booking.Features.CreatingBook.Commands.V1;
public sealed class FakeCreateBookingCommand : AutoFaker<CreateBooking>
{
public FakeCreateBookingCommand()
{

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 BuildingBlocks.Exception;
namespace Flight.Aircrafts.Features.CreateAircraft.Exceptions;
public class AircraftAlreadyExistException : AppException
{
public AircraftAlreadyExistException() : base("Aircraft already exist!", HttpStatusCode.Conflict)

View File

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

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

View File

@ -1,23 +1,26 @@
using System.Threading;
namespace Flight.Aircrafts.Features.CreatingAircraft.V1;
using System.Threading;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using Flight.Aircrafts.Features.CreateAircraft.Exceptions;
using Flight.Aircrafts.Models.Reads;
using BuildingBlocks.Core.Event;
using Exceptions;
using Flight.Aircrafts.Models;
using Flight.Data;
using MapsterMapper;
using MediatR;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
public record CreateAircraftMongo(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : InternalCommand;
public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftMongoCommand>
public class CreateAircraftMongoHandler : ICommandHandler<CreateAircraftMongo>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public CreateAircraftMongoCommandHandler(
public CreateAircraftMongoHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
@ -25,17 +28,19 @@ public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftM
_mapper = mapper;
}
public async Task<Unit> Handle(CreateAircraftMongoCommand command, CancellationToken cancellationToken)
public async Task<Unit> Handle(CreateAircraftMongo request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var aircraftReadModel = _mapper.Map<AircraftReadModel>(command);
var aircraftReadModel = _mapper.Map<AircraftReadModel>(request);
var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
.FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken);
if (aircraft is not null)
{
throw new AircraftAlreadyExistException();
}
await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken);

View File

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

View File

@ -1,4 +1,4 @@
namespace Flight.Aircrafts.Models.Reads;
namespace Flight.Aircrafts.Models;
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
{

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

View File

@ -1,23 +1,26 @@
using System.Threading;
namespace Flight.Airports.Features.CreatingAirport.V1;
using System.Threading;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using Flight.Airports.Features.CreateAirport.Exceptions;
using Flight.Airports.Models.Reads;
using Flight.Data;
using BuildingBlocks.Core.Event;
using Exceptions;
using Models;
using Data;
using MapsterMapper;
using MediatR;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
public record CreateAirportMongo(long Id, string Name, string Address, string Code, bool IsDeleted) : InternalCommand;
public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMongoCommand>
internal class CreateAirportMongoHandler : ICommandHandler<CreateAirportMongo>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public CreateAirportMongoCommandHandler(
public CreateAirportMongoHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
@ -25,17 +28,19 @@ public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMon
_mapper = mapper;
}
public async Task<Unit> Handle(CreateAirportMongoCommand command, CancellationToken cancellationToken)
public async Task<Unit> Handle(CreateAirportMongo request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var airportReadModel = _mapper.Map<AirportReadModel>(command);
var airportReadModel = _mapper.Map<AirportReadModel>(request);
var aircraft = await _flightReadDbContext.Airport.AsQueryable()
.FirstOrDefaultAsync(x => x.AirportId == airportReadModel.AirportId, cancellationToken);
if (aircraft is not null)
{
throw new AirportAlreadyExistException();
}
await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,17 @@
using BuildingBlocks.Contracts.EventBus.Messages;
using BuildingBlocks.Core;
using BuildingBlocks.Core.Event;
using Flight.Aircrafts.Features.CreateAircraft.Commands.V1.Reads;
using Flight.Aircrafts.Features.CreateAircraft.Events.Domain.V1;
using Flight.Airports.Features.CreateAirport.Commands.V1.Reads;
using Flight.Airports.Features.CreateAirport.Events.Domain.V1;
using Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
using Flight.Flights.Features.CreateFlight.Events.Domain.V1;
using Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
using Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
using Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
using Flight.Flights.Features.UpdateFlight.Events.V1;
using Flight.Seats.Features.CreateSeat.Commands.V1.Reads;
using Flight.Seats.Features.CreateSeat.Events.Domain.V1;
using Flight.Seats.Features.ReserveSeat.Commands.V1.Reads;
using Flight.Seats.Features.ReserveSeat.Events.Domain.V1;
namespace Flight;
using Aircrafts.Features.CreatingAircraft.V1;
using Airports.Features.CreatingAirport.V1;
using Flights.Features.CreatingFlight.V1;
using Flights.Features.DeletingFlight.V1;
using Flights.Features.UpdatingFlight.V1;
using Seats.Features.CreatingSeat.V1;
using Seats.Features.ReservingSeat.Commands.V1;
// ref: https://www.ledjonbehluli.com/posts/domain_to_integration_event/
public sealed class EventMapper : IEventMapper
{
@ -40,16 +34,16 @@ public sealed class EventMapper : IEventMapper
{
return @event switch
{
FlightCreatedDomainEvent e => new CreateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
FlightUpdatedDomainEvent e => new UpdateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
FlightUpdatedDomainEvent e => new UpdateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
FlightDeletedDomainEvent e => new DeleteFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
FlightDeletedDomainEvent e => new DeleteFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
AircraftCreatedDomainEvent e => new CreateAircraftMongoCommand(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
AirportCreatedDomainEvent e => new CreateAirportMongoCommand(e.Id, e.Name, e.Address, e.Code, e.IsDeleted),
SeatCreatedDomainEvent e => new CreateSeatMongoCommand(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
SeatReservedDomainEvent e => new ReserveSeatMongoCommand(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
AircraftCreatedDomainEvent e => new CreateAircraftMongo(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
AirportCreatedDomainEvent e => new CreateAirportMongo(e.Id, e.Name, e.Address, e.Code, e.IsDeleted),
SeatCreatedDomainEvent e => new CreateSeatMongo(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
SeatReservedDomainEvent e => new ReserveSeatMongo(e.Id, e.SeatNumber, e.Type, e.Class, e.FlightId, e.IsDeleted),
_ => null
};
}

View File

@ -19,7 +19,6 @@
<ItemGroup>
<Folder Include="Data\Migrations" />
<Folder Include="Seats\Features\CreateSeat\Events\Domain" />
</ItemGroup>
<ItemGroup>
@ -30,4 +29,20 @@
<ProjectReference Include="..\..\..\..\BuildingBlocks\BuildingBlocks.csproj" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Unit.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Integration.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>EndToEnd.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@ -3,6 +3,6 @@ using Flight.Flights.Models;
namespace Flight.Flights.Dtos;
public record FlightResponseDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
public record FlightDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId,
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price);

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
{

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

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 Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using BuildingBlocks.Core.Event;
using Flight.Data;
using Flight.Flights.Exceptions;
using Flight.Flights.Features.CreateFlight.Exceptions;
using Flight.Flights.Models.Reads;
using MapsterMapper;
using MediatR;
using Models;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Flight.Flights.Features.CreateFlight.Commands.V1.Reads;
public record CreateFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongoCommand>
internal class CreateFlightMongoHandler : ICommandHandler<CreateFlightMongo>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
public CreateFlightMongoCommandHandler(
public CreateFlightMongoHandler(
FlightReadDbContext flightReadDbContext,
IMapper mapper)
{
@ -26,17 +31,19 @@ public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongo
_mapper = mapper;
}
public async Task<Unit> Handle(CreateFlightMongoCommand command, CancellationToken cancellationToken)
public async Task<Unit> Handle(CreateFlightMongo request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var flightReadModel = _mapper.Map<FlightReadModel>(command);
var flightReadModel = _mapper.Map<FlightReadModel>(request);
var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is not null)
{
throw new FlightAlreadyExistException();
}
await _flightReadDbContext.Flight.InsertOneAsync(flightReadModel, cancellationToken: cancellationToken);

View File

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

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 Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using Flight.Data;
using Flight.Flights.Dtos;
using Flight.Flights.Exceptions;
using BuildingBlocks.Core.Event;
using Data;
using Dtos;
using Exceptions;
using FluentValidation;
using MapsterMapper;
using Microsoft.EntityFrameworkCore;
namespace Flight.Flights.Features.DeleteFlight.Commands.V1;
public record DeleteFlight(long Id) : ICommand<FlightDto>, IInternalCommand;
public class DeleteFlightCommandHandler : ICommandHandler<DeleteFlightCommand, FlightResponseDto>
internal class DeleteFlightValidator : AbstractValidator<DeleteFlight>
{
public DeleteFlightValidator()
{
RuleFor(x => x.Id).NotEmpty();
}
}
internal class DeleteFlightHandler : ICommandHandler<DeleteFlight, FlightDto>
{
private readonly FlightDbContext _flightDbContext;
private readonly IMapper _mapper;
public DeleteFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
public DeleteFlightHandler(IMapper mapper, FlightDbContext flightDbContext)
{
_mapper = mapper;
_flightDbContext = flightDbContext;
}
public async Task<FlightResponseDto> Handle(DeleteFlightCommand command, CancellationToken cancellationToken)
public async Task<FlightDto> Handle(DeleteFlight request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == command.Id, cancellationToken);
var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == request.Id, cancellationToken);
if (flight is null)
{
throw new FlightNotFountException();
}
var deleteFlight = _flightDbContext.Flights.Remove(flight).Entity;
@ -37,6 +50,6 @@ public class DeleteFlightCommandHandler : ICommandHandler<DeleteFlightCommand, F
deleteFlight.DepartureDate, deleteFlight.ArriveDate, deleteFlight.ArriveAirportId, deleteFlight.DurationMinutes,
deleteFlight.FlightDate, deleteFlight.Status, deleteFlight.Price);
return _mapper.Map<FlightResponseDto>(deleteFlight);
return _mapper.Map<FlightDto>(deleteFlight);
}
}

View File

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

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 Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using Flight.Data;
using Flight.Flights.Exceptions;
using Flight.Flights.Models.Reads;
using BuildingBlocks.Core.Event;
using Data;
using Exceptions;
using MapsterMapper;
using MediatR;
using Models;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Flight.Flights.Features.DeleteFlight.Commands.V1.Reads;
public record DeleteFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongoCommand>
internal class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongo>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
@ -25,17 +31,19 @@ public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongo
_mapper = mapper;
}
public async Task<Unit> Handle(DeleteFlightMongoCommand command, CancellationToken cancellationToken)
public async Task<Unit> Handle(DeleteFlightMongo request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var flightReadModel = _mapper.Map<FlightReadModel>(command);
var flightReadModel = _mapper.Map<FlightReadModel>(request);
var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is null)
{
throw new FlightNotFountException();
}
await _flightReadDbContext.Flight.UpdateOneAsync(
x => x.FlightId == flightReadModel.FlightId,

View File

@ -1,7 +1,7 @@
using System;
using BuildingBlocks.Core.Event;
namespace Flight.Flights.Features.DeletingFlight.V1;
namespace Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
using System;
using BuildingBlocks.Core.Event;
public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,

View File

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

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.Threading;
using System.Threading.Tasks;
using BuildingBlocks.Web;
using Flight.Flights.Dtos;
using Flight.Flights.Features.GetAvailableFlights.Queries.V1;
using Hellang.Middleware.ProblemDetails;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.GetAvailableFlights.Endpoints.V1;
using Hellang.Middleware.ProblemDetails;
public class GetAvailableFlightsEndpoint : IMinimalEndpoint
{
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
{
endpoints.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
builder.MapGet($"{EndpointConfig.BaseApiPath}/flight/get-available-flights", GetAvailableFlights)
.RequireAuthorization()
.WithTags("Flight")
.WithName("GetAvailableFlights")
.WithMetadata(new SwaggerOperationAttribute("Get Available Flights", "Get Available Flights"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.WithMetadata(
new SwaggerResponseAttribute(
StatusCodes.Status200OK,
"GetAvailableFlights",
typeof(IEnumerable<FlightResponseDto>)))
typeof(IEnumerable<FlightDto>)))
.WithMetadata(
new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest,
@ -41,12 +39,12 @@ public class GetAvailableFlightsEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0);
return endpoints;
return builder;
}
private async Task<IResult> GetAvailableFlights(IMediator mediator, CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAvailableFlightsQuery(), cancellationToken);
var result = await mediator.Send(new GetAvailableFlights(), cancellationToken);
return Results.Ok(result);
}

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

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 BuildingBlocks.Core.Event;
namespace Flight.Flights.Features.UpdateFlight.Events.V1;
public record FlightUpdatedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes,
DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent;

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.Tasks;
using BuildingBlocks.Web;
using Flight.Flights.Dtos;
using Flight.Flights.Features.UpdateFlight.Commands.V1;
using Flight.Flights.Features.UpdateFlight.Dtos;
using Dtos;
using Hellang.Middleware.ProblemDetails;
using MapsterMapper;
using MediatR;
using Microsoft.AspNetCore.Builder;
@ -11,28 +13,27 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Swashbuckle.AspNetCore.Annotations;
namespace Flight.Flights.Features.UpdateFlight.Endpoints.V1;
using Hellang.Middleware.ProblemDetails;
public record UpdateFlightRequestDto(long Id, string FlightNumber, long AircraftId, long DepartureAirportId, DateTime DepartureDate, DateTime ArriveDate,
long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted);
public class UpdateFlightEndpoint : IMinimalEndpoint
{
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder endpoints)
public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder)
{
endpoints.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
builder.MapPut($"{EndpointConfig.BaseApiPath}/flight", UpdateFlight)
.RequireAuthorization()
.WithTags("Flight")
.WithName("UpdateFlight")
.WithMetadata(new SwaggerOperationAttribute("Update Flight", "Update Flight"))
.WithApiVersionSet(endpoints.NewApiVersionSet("Flight").Build())
.Produces<FlightResponseDto>()
.WithApiVersionSet(builder.NewApiVersionSet("Flight").Build())
.Produces<FlightDto>()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status400BadRequest)
.WithMetadata(
new SwaggerResponseAttribute(
StatusCodes.Status204NoContent,
"Flight Updated",
typeof(FlightResponseDto)))
typeof(FlightDto)))
.WithMetadata(
new SwaggerResponseAttribute(
StatusCodes.Status400BadRequest,
@ -45,12 +46,12 @@ public class UpdateFlightEndpoint : IMinimalEndpoint
typeof(StatusCodeProblemDetails)))
.HasApiVersion(1.0);
return endpoints;
return builder;
}
private async Task<IResult> UpdateFlight(UpdateFlightRequestDto request, IMediator mediator, IMapper mapper, CancellationToken cancellationToken)
{
var command = mapper.Map<UpdateFlightCommand>(request);
var command = mapper.Map<UpdateFlight>(request);
var result = await mediator.Send(command, cancellationToken);

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 Ardalis.GuardClauses;
using BuildingBlocks.Core.CQRS;
using Flight.Data;
using Flight.Flights.Exceptions;
using Flight.Flights.Models.Reads;
using BuildingBlocks.Core.Event;
using Data;
using Exceptions;
using MapsterMapper;
using MediatR;
using Models;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
namespace Flight.Flights.Features.UpdateFlight.Commands.V1.Reads;
public record UpdateFlightMongo(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate,
Enums.FlightStatus Status, decimal Price, bool IsDeleted) : InternalCommand;
public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongoCommand>
internal class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo>
{
private readonly FlightReadDbContext _flightReadDbContext;
private readonly IMapper _mapper;
@ -25,17 +32,19 @@ public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongo
_mapper = mapper;
}
public async Task<Unit> Handle(UpdateFlightMongoCommand command, CancellationToken cancellationToken)
public async Task<Unit> Handle(UpdateFlightMongo request, CancellationToken cancellationToken)
{
Guard.Against.Null(command, nameof(command));
Guard.Against.Null(request, nameof(request));
var flightReadModel = _mapper.Map<FlightReadModel>(command);
var flightReadModel = _mapper.Map<FlightReadModel>(request);
var flight = await _flightReadDbContext.Flight.AsQueryable()
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId && !x.IsDeleted, cancellationToken);
if (flight is null)
{
throw new FlightNotFountException();
}
await _flightReadDbContext.Flight.UpdateOneAsync(
x => x.FlightId == flightReadModel.FlightId,

View File

@ -1,11 +1,12 @@
using System;
using BuildingBlocks.Core.Model;
using Flight.Flights.Features.CreateFlight.Events.Domain.V1;
using Flight.Flights.Features.DeleteFlight.Events.Domain.V1;
using Flight.Flights.Features.UpdateFlight.Events.V1;
namespace Flight.Flights.Models;
using Features.CreatingFlight.V1;
using Features.DeletingFlight.V1;
using Features.UpdatingFlight.V1;
public record Flight : Aggregate<long>
{
public string FlightNumber { get; private set; }

View File

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

View File

@ -1,16 +1,14 @@
using System.Threading.Tasks;
using Flight.Flights.Features.GetFlightById;
using Flight.Flights.Features.GetFlightById.Queries.V1;
using Flight.Seats.Features.GetAvailableSeats;
using Flight.Seats.Features.GetAvailableSeats.Queries.V1;
using Flight.Seats.Features.ReserveSeat;
using Flight.Seats.Features.ReserveSeat.Commands.V1;
using Grpc.Core;
using Mapster;
using MediatR;
namespace Flight.GrpcServer.Services;
using Flights.Features.GettingFlightById.V1;
using Seats.Features.GettingAvailableSeats.V1;
using Seats.Features.ReservingSeat.Commands.V1;
public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
{
private readonly IMediator _mediator;
@ -22,13 +20,13 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
public override async Task<FlightResponse> GetById(GetByIdRequest request, ServerCallContext context)
{
var result = await _mediator.Send(new GetFlightByIdQuery(request.Id));
var result = await _mediator.Send(new GetFlightById(request.Id));
return result.Adapt<FlightResponse>();
}
public override async Task<SeatsResponse> ReserveSeat(ReserveSeatRequest request, ServerCallContext context)
{
var result = await _mediator.Send(new ReserveSeatCommand(request.FlightId, request.SeatNumber));
var result = await _mediator.Send(new ReserveSeat(request.FlightId, request.SeatNumber));
return result.Adapt<SeatsResponse>();
}
@ -36,7 +34,7 @@ public class FlightGrpcServices : FlightGrpcService.FlightGrpcServiceBase
{
var result = new ListSeatsResponse();
var availableSeats = await _mediator.Send(new GetAvailableSeatsQuery(request.FlightId));
var availableSeats = await _mediator.Send(new GetAvailableSeats(request.FlightId));
foreach (var availableSeat in availableSeats)
{

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
{

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
{

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
{

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