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