mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-27 08:00:52 +08:00
- add flight read models
- add aircraft read models - add airport read models
This commit is contained in:
parent
58e93a0761
commit
a6f7bd46d5
14
booking.rest
14
booking.rest
@ -143,6 +143,20 @@ authorization: bearer {{Authenticate.response.body.access_token}}
|
|||||||
}
|
}
|
||||||
###
|
###
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# @name Delete_Flights
|
||||||
|
DELETE {{api-gateway}}/api/v1/flight
|
||||||
|
accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
authorization: bearer {{Authenticate.response.body.access_token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# @name Create_Airport
|
# @name Create_Airport
|
||||||
POST {{api-gateway}}/api/v1/flight/airport
|
POST {{api-gateway}}/api/v1/flight/airport
|
||||||
|
|||||||
@ -3,6 +3,7 @@ namespace BuildingBlocks.Core.Event;
|
|||||||
[Flags]
|
[Flags]
|
||||||
public enum EventType
|
public enum EventType
|
||||||
{
|
{
|
||||||
IntegrationEvent = 1,
|
DomainEvent = 1,
|
||||||
DomainEvent = 2
|
IntegrationEvent = 2,
|
||||||
|
InternalCommand = 4
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
using BuildingBlocks.Core.CQRS;
|
|
||||||
|
|
||||||
namespace BuildingBlocks.Core.Event;
|
|
||||||
|
|
||||||
public interface IInternalCommand : ICommand
|
|
||||||
{
|
|
||||||
long Id { get; }
|
|
||||||
DateTime OccurredOn { get; }
|
|
||||||
string Type { get; }
|
|
||||||
}
|
|
||||||
11
src/BuildingBlocks/Core/Event/IInternalCommnad.cs
Normal file
11
src/BuildingBlocks/Core/Event/IInternalCommnad.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using BuildingBlocks.Utils;
|
||||||
|
|
||||||
|
public interface IInternalCommand
|
||||||
|
{
|
||||||
|
public long Id => SnowFlakIdGenerator.NewId();
|
||||||
|
|
||||||
|
public DateTime OccurredOn => DateTime.Now;
|
||||||
|
|
||||||
|
public string Type => TypeProvider.GetTypeName(GetType());
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
using BuildingBlocks.IdsGenerator;
|
using BuildingBlocks.IdsGenerator;
|
||||||
using BuildingBlocks.Utils;
|
using BuildingBlocks.Utils;
|
||||||
|
using ICommand = BuildingBlocks.Core.CQRS.ICommand;
|
||||||
|
|
||||||
namespace BuildingBlocks.Core.Event;
|
namespace BuildingBlocks.Core.Event;
|
||||||
|
|
||||||
public class InternalCommand : IInternalCommand
|
public class InternalCommand : IInternalCommand, ICommand
|
||||||
{
|
{
|
||||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||||
|
|
||||||
|
|||||||
@ -31,20 +31,22 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task SendAsync<T>(IReadOnlyList<T> events, CancellationToken cancellationToken = default)
|
public async Task SendAsync<T>(IReadOnlyList<T> events, EventType eventType = default,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
where T : IEvent
|
where T : IEvent
|
||||||
{
|
{
|
||||||
async Task PublishIntegrationEvent(IReadOnlyList<IIntegrationEvent> integrationEvents)
|
|
||||||
{
|
|
||||||
foreach (var integrationEvent in integrationEvents)
|
|
||||||
{
|
|
||||||
await _persistMessageProcessor.PublishMessageAsync(new MessageEnvelope(integrationEvent, SetHeaders()),
|
|
||||||
cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events.Count > 0)
|
if (events.Count > 0)
|
||||||
{
|
{
|
||||||
|
async Task PublishIntegrationEvent(IReadOnlyList<IIntegrationEvent> integrationEvents)
|
||||||
|
{
|
||||||
|
foreach (var integrationEvent in integrationEvents)
|
||||||
|
{
|
||||||
|
await _persistMessageProcessor.PublishMessageAsync(
|
||||||
|
new MessageEnvelope(integrationEvent, SetHeaders()),
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (events)
|
switch (events)
|
||||||
{
|
{
|
||||||
case IReadOnlyList<IDomainEvent> domainEvents:
|
case IReadOnlyList<IDomainEvent> domainEvents:
|
||||||
@ -60,12 +62,24 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
await PublishIntegrationEvent(integrationEvents);
|
await PublishIntegrationEvent(integrationEvents);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType == EventType.InternalCommand)
|
||||||
|
{
|
||||||
|
var internalMessages = await MapDomainEventToInternalCommandAsync(events as IReadOnlyList<IDomainEvent>)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var internalMessage in internalMessages)
|
||||||
|
{
|
||||||
|
await _persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync<T>(T @event, CancellationToken cancellationToken = default)
|
public async Task SendAsync<T>(T @event, EventType eventType = default,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
where T : IEvent =>
|
where T : IEvent =>
|
||||||
await SendAsync(new[] {@event}, cancellationToken);
|
await SendAsync(new[] {@event}, eventType, cancellationToken);
|
||||||
|
|
||||||
|
|
||||||
private Task<IReadOnlyList<IIntegrationEvent>> MapDomainEventToIntegrationEventAsync(
|
private Task<IReadOnlyList<IIntegrationEvent>> MapDomainEventToIntegrationEventAsync(
|
||||||
@ -84,7 +98,7 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
var eventType = @event.GetType();
|
var eventType = @event.GetType();
|
||||||
_logger.LogTrace($"Handling domain event: {eventType.Name}");
|
_logger.LogTrace($"Handling domain event: {eventType.Name}");
|
||||||
|
|
||||||
var integrationEvent = _eventMapper.Map(@event);
|
var integrationEvent = _eventMapper.MapToIntegrationEvent(@event);
|
||||||
|
|
||||||
if (integrationEvent is null) continue;
|
if (integrationEvent is null) continue;
|
||||||
|
|
||||||
@ -96,6 +110,31 @@ public sealed class EventDispatcher : IEventDispatcher
|
|||||||
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(integrationEvents);
|
return Task.FromResult<IReadOnlyList<IIntegrationEvent>>(integrationEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Task<IReadOnlyList<InternalCommand>> MapDomainEventToInternalCommandAsync(
|
||||||
|
IReadOnlyList<IDomainEvent> events)
|
||||||
|
{
|
||||||
|
_logger.LogTrace("Processing internal message start...");
|
||||||
|
|
||||||
|
var internalCommands = new List<InternalCommand>();
|
||||||
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
foreach (var @event in events)
|
||||||
|
{
|
||||||
|
var eventType = @event.GetType();
|
||||||
|
_logger.LogTrace($"Handling domain event: {eventType.Name}");
|
||||||
|
|
||||||
|
var integrationEvent = _eventMapper.MapToInternalCommand(@event);
|
||||||
|
|
||||||
|
if (integrationEvent is null) continue;
|
||||||
|
|
||||||
|
internalCommands.Add(integrationEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogTrace("Processing internal message done...");
|
||||||
|
|
||||||
|
return Task.FromResult<IReadOnlyList<InternalCommand>>(internalCommands);
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<IIntegrationEvent> GetWrappedIntegrationEvents(IReadOnlyList<IDomainEvent> domainEvents)
|
private IEnumerable<IIntegrationEvent> GetWrappedIntegrationEvents(IReadOnlyList<IDomainEvent> domainEvents)
|
||||||
{
|
{
|
||||||
foreach (var domainEvent in domainEvents.Where(x =>
|
foreach (var domainEvent in domainEvents.Where(x =>
|
||||||
|
|||||||
@ -4,8 +4,8 @@ namespace BuildingBlocks.Core;
|
|||||||
|
|
||||||
public interface IEventDispatcher
|
public interface IEventDispatcher
|
||||||
{
|
{
|
||||||
public Task SendAsync<T>(IReadOnlyList<T> events, CancellationToken cancellationToken = default)
|
public Task SendAsync<T>(IReadOnlyList<T> events, EventType eventType = default, CancellationToken cancellationToken = default)
|
||||||
where T : IEvent;
|
where T : IEvent;
|
||||||
public Task SendAsync<T>(T @event, CancellationToken cancellationToken = default)
|
public Task SendAsync<T>(T @event, EventType eventType = default, CancellationToken cancellationToken = default)
|
||||||
where T : IEvent;
|
where T : IEvent;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,6 @@ namespace BuildingBlocks.Core;
|
|||||||
|
|
||||||
public interface IEventMapper
|
public interface IEventMapper
|
||||||
{
|
{
|
||||||
IIntegrationEvent Map(IDomainEvent @event);
|
IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event);
|
||||||
IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events);
|
InternalCommand MapToInternalCommand(IDomainEvent @event);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,7 +61,9 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
|
|||||||
|
|
||||||
var domainEvents = _dbContextBase.GetDomainEvents();
|
var domainEvents = _dbContextBase.GetDomainEvents();
|
||||||
|
|
||||||
await _eventDispatcher.SendAsync(domainEvents.ToArray(), cancellationToken);
|
var eventType = typeof(TRequest).IsAssignableTo(typeof(IInternalCommand)) ? EventType.InternalCommand : EventType.DomainEvent;
|
||||||
|
|
||||||
|
await _eventDispatcher.SendAsync(domainEvents.ToArray(), eventType, cancellationToken);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,12 +23,15 @@ public static class Extensions
|
|||||||
var mongoOptions = services.GetOptions<MongoOptions>("MongoOptions");
|
var mongoOptions = services.GetOptions<MongoOptions>("MongoOptions");
|
||||||
var logOptions = services.GetOptions<LogOptions>("LogOptions");
|
var logOptions = services.GetOptions<LogOptions>("LogOptions");
|
||||||
|
|
||||||
services.AddHealthChecks()
|
var healthChecksBuilder = services.AddHealthChecks()
|
||||||
.AddSqlServer(sqlOptions.DefaultConnection)
|
.AddSqlServer(sqlOptions.DefaultConnection)
|
||||||
.AddMongoDb(mongoOptions.ConnectionString)
|
|
||||||
.AddRabbitMQ(rabbitConnectionString: $"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}")
|
.AddRabbitMQ(rabbitConnectionString: $"amqp://{rabbitMqOptions.UserName}:{rabbitMqOptions.Password}@{rabbitMqOptions.HostName}")
|
||||||
.AddElasticsearch(logOptions.ElasticUri);
|
.AddElasticsearch(logOptions.ElasticUri);
|
||||||
|
|
||||||
|
if (mongoOptions.ConnectionString is not null)
|
||||||
|
healthChecksBuilder.AddMongoDb(mongoOptions.ConnectionString);
|
||||||
|
|
||||||
|
|
||||||
services.AddHealthChecksUI(setup =>
|
services.AddHealthChecksUI(setup =>
|
||||||
{
|
{
|
||||||
setup.SetEvaluationTimeInSeconds(60); // time in seconds between check
|
setup.SetEvaluationTimeInSeconds(60); // time in seconds between check
|
||||||
|
|||||||
@ -7,12 +7,7 @@ namespace Booking;
|
|||||||
|
|
||||||
public sealed class EventMapper : IEventMapper
|
public sealed class EventMapper : IEventMapper
|
||||||
{
|
{
|
||||||
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events)
|
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
|
||||||
{
|
|
||||||
return events.Select(Map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IIntegrationEvent Map(IDomainEvent @event)
|
|
||||||
{
|
{
|
||||||
return @event switch
|
return @event switch
|
||||||
{
|
{
|
||||||
@ -20,4 +15,12 @@ public sealed class EventMapper : IEventMapper
|
|||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InternalCommand MapToInternalCommand(IDomainEvent @event)
|
||||||
|
{
|
||||||
|
return @event switch
|
||||||
|
{
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,7 @@ using Flight.Data;
|
|||||||
using Flight.Data.Seed;
|
using Flight.Data.Seed;
|
||||||
using Flight.Extensions;
|
using Flight.Extensions;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using HealthChecks.UI.Client;
|
|
||||||
using Hellang.Middleware.ProblemDetails;
|
using Hellang.Middleware.ProblemDetails;
|
||||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|||||||
@ -2,4 +2,4 @@ using BuildingBlocks.Core.Event;
|
|||||||
|
|
||||||
namespace Flight.Aircrafts.Events;
|
namespace Flight.Aircrafts.Events;
|
||||||
|
|
||||||
public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear) : IDomainEvent;
|
public record AircraftCreatedDomainEvent(long Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Flight.Aircrafts.Features.CreateAircraft.Reads;
|
||||||
|
using Flight.Aircrafts.Models.Reads;
|
||||||
|
using Mapster;
|
||||||
|
|
||||||
|
namespace Flight.Aircrafts.Features;
|
||||||
|
|
||||||
|
public class AircraftMappings : IRegister
|
||||||
|
{
|
||||||
|
public void Register(TypeAdapterConfig config)
|
||||||
|
{
|
||||||
|
config.NewConfig<CreateAircraftMongoCommand, AircraftReadModel>()
|
||||||
|
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||||
|
.Map(d => d.AircraftId, s => s.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ using MediatR;
|
|||||||
|
|
||||||
namespace Flight.Aircrafts.Features.CreateAircraft;
|
namespace Flight.Aircrafts.Features.CreateAircraft;
|
||||||
|
|
||||||
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest<AircraftResponseDto>
|
public record CreateAircraftCommand(string Name, string Model, int ManufacturingYear) : IRequest<AircraftResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
using BuildingBlocks.Core.Event;
|
||||||
|
|
||||||
|
namespace Flight.Aircrafts.Features.CreateAircraft.Reads;
|
||||||
|
|
||||||
|
public class CreateAircraftMongoCommand : InternalCommand
|
||||||
|
{
|
||||||
|
public CreateAircraftMongoCommand(long id, string name, string model, int manufacturingYear, bool isDeleted)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Model = model;
|
||||||
|
ManufacturingYear = manufacturingYear;
|
||||||
|
IsDeleted = isDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public long Id { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public string Model { get; }
|
||||||
|
public int ManufacturingYear { get; }
|
||||||
|
public bool IsDeleted { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using BuildingBlocks.Core.CQRS;
|
||||||
|
using Flight.Aircrafts.Exceptions;
|
||||||
|
using Flight.Aircrafts.Models.Reads;
|
||||||
|
using Flight.Data;
|
||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace Flight.Aircrafts.Features.CreateAircraft.Reads;
|
||||||
|
|
||||||
|
public class CreateAircraftMongoCommandHandler : ICommandHandler<CreateAircraftMongoCommand>
|
||||||
|
{
|
||||||
|
private readonly FlightReadDbContext _flightReadDbContext;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public CreateAircraftMongoCommandHandler(
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_flightReadDbContext = flightReadDbContext;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(CreateAircraftMongoCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(command, nameof(command));
|
||||||
|
|
||||||
|
var aircraftReadModel = _mapper.Map<AircraftReadModel>(command);
|
||||||
|
|
||||||
|
var aircraft = await _flightReadDbContext.Aircraft.AsQueryable()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == aircraftReadModel.Id, cancellationToken);
|
||||||
|
|
||||||
|
if (aircraft is not null)
|
||||||
|
throw new AircraftAlreadyExistException();
|
||||||
|
|
||||||
|
await _flightReadDbContext.Aircraft.InsertOneAsync(aircraftReadModel, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@ public class Aircraft : Aggregate<long>
|
|||||||
public string Model { get; private set; }
|
public string Model { get; private set; }
|
||||||
public int ManufacturingYear { get; private set; }
|
public int ManufacturingYear { get; private set; }
|
||||||
|
|
||||||
public static Aircraft Create(long id, string name, string model, int manufacturingYear)
|
public static Aircraft Create(long id, string name, string model, int manufacturingYear, bool isDeleted = false)
|
||||||
{
|
{
|
||||||
var aircraft = new Aircraft
|
var aircraft = new Aircraft
|
||||||
{
|
{
|
||||||
@ -28,7 +28,8 @@ public class Aircraft : Aggregate<long>
|
|||||||
aircraft.Id,
|
aircraft.Id,
|
||||||
aircraft.Name,
|
aircraft.Name,
|
||||||
aircraft.Model,
|
aircraft.Model,
|
||||||
aircraft.ManufacturingYear);
|
aircraft.ManufacturingYear,
|
||||||
|
isDeleted);
|
||||||
|
|
||||||
aircraft.AddDomainEvent(@event);
|
aircraft.AddDomainEvent(@event);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
namespace Flight.Aircrafts.Models.Reads;
|
||||||
|
|
||||||
|
public class AircraftReadModel
|
||||||
|
{
|
||||||
|
public long Id { get; init; }
|
||||||
|
public long AircraftId { get; init; }
|
||||||
|
public string Name { get; init; }
|
||||||
|
public string Model { get; init; }
|
||||||
|
public int ManufacturingYear { get; init; }
|
||||||
|
public bool IsDeleted { get; init; }
|
||||||
|
}
|
||||||
16
src/Services/Flight/src/Flight/Airports/AirportMappings.cs
Normal file
16
src/Services/Flight/src/Flight/Airports/AirportMappings.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
|
using Flight.Airports.Features.CreateAirport.Reads;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,4 +2,4 @@ using BuildingBlocks.Core.Event;
|
|||||||
|
|
||||||
namespace Flight.Airports.Events;
|
namespace Flight.Airports.Events;
|
||||||
|
|
||||||
public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code) : IDomainEvent;
|
public record AirportCreatedDomainEvent(long Id, string Name, string Address, string Code, bool IsDeleted) : IDomainEvent;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using MediatR;
|
|||||||
|
|
||||||
namespace Flight.Airports.Features.CreateAirport;
|
namespace Flight.Airports.Features.CreateAirport;
|
||||||
|
|
||||||
public record CreateAirportCommand(string Name, string Address, string Code) : IRequest<AirportResponseDto>
|
public record CreateAirportCommand(string Name, string Address, string Code) : IRequest<AirportResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
using BuildingBlocks.Core.Event;
|
||||||
|
|
||||||
|
namespace Flight.Airports.Features.CreateAirport.Reads;
|
||||||
|
|
||||||
|
public class CreateAirportMongoCommand : InternalCommand
|
||||||
|
{
|
||||||
|
public CreateAirportMongoCommand(long id, string name, string address, string code, bool isDeleted)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
Address = address;
|
||||||
|
Code = code;
|
||||||
|
IsDeleted = isDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long Id { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public string Address { get; }
|
||||||
|
public string Code { get; }
|
||||||
|
public bool IsDeleted { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using BuildingBlocks.Core.CQRS;
|
||||||
|
using Flight.Airports.Exceptions;
|
||||||
|
using Flight.Airports.Models.Reads;
|
||||||
|
using Flight.Data;
|
||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace Flight.Airports.Features.CreateAirport.Reads;
|
||||||
|
|
||||||
|
public class CreateAirportMongoCommandHandler : ICommandHandler<CreateAirportMongoCommand>
|
||||||
|
{
|
||||||
|
private readonly FlightReadDbContext _flightReadDbContext;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public CreateAirportMongoCommandHandler(
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_flightReadDbContext = flightReadDbContext;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(CreateAirportMongoCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(command, nameof(command));
|
||||||
|
|
||||||
|
var airportReadModel = _mapper.Map<AirportReadModel>(command);
|
||||||
|
|
||||||
|
var aircraft = await _flightReadDbContext.Airport.AsQueryable()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == airportReadModel.Id, cancellationToken);
|
||||||
|
|
||||||
|
if (aircraft is not null)
|
||||||
|
throw new AirportAlreadyExistException();
|
||||||
|
|
||||||
|
await _flightReadDbContext.Airport.InsertOneAsync(airportReadModel, cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,7 @@ public class Airport : Aggregate<long>
|
|||||||
public string Address { get; private set; }
|
public string Address { get; private set; }
|
||||||
public string Code { get; private set; }
|
public string Code { get; private set; }
|
||||||
|
|
||||||
public static Airport Create(long id, string name, string address, string code)
|
public static Airport Create(long id, string name, string address, string code, bool isDeleted = false)
|
||||||
{
|
{
|
||||||
var airport = new Airport
|
var airport = new Airport
|
||||||
{
|
{
|
||||||
@ -28,7 +28,8 @@ public class Airport : Aggregate<long>
|
|||||||
airport.Id,
|
airport.Id,
|
||||||
airport.Name,
|
airport.Name,
|
||||||
airport.Address,
|
airport.Address,
|
||||||
airport.Code);
|
airport.Code,
|
||||||
|
isDeleted);
|
||||||
|
|
||||||
airport.AddDomainEvent(@event);
|
airport.AddDomainEvent(@event);
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
namespace Flight.Airports.Models.Reads;
|
||||||
|
|
||||||
|
public class AirportReadModel
|
||||||
|
{
|
||||||
|
public long Id { get; init; }
|
||||||
|
public long AirportId { get; init; }
|
||||||
|
public string Name { get; init; }
|
||||||
|
public string Address { get; init; }
|
||||||
|
public string Code { get; init; }
|
||||||
|
public bool IsDeleted { get; set; }
|
||||||
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
using BuildingBlocks.Mongo;
|
using BuildingBlocks.Mongo;
|
||||||
|
using Flight.Aircrafts.Models.Reads;
|
||||||
|
using Flight.Airports.Models;
|
||||||
|
using Flight.Airports.Models.Reads;
|
||||||
using Flight.Flights.Models.Reads;
|
using Flight.Flights.Models.Reads;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@ -11,7 +14,11 @@ public class FlightReadDbContext : MongoDbContext
|
|||||||
public FlightReadDbContext(IOptions<MongoOptions> options) : base(options)
|
public FlightReadDbContext(IOptions<MongoOptions> options) : base(options)
|
||||||
{
|
{
|
||||||
Flight = GetCollection<FlightReadModel>(nameof(Flight).Underscore());
|
Flight = GetCollection<FlightReadModel>(nameof(Flight).Underscore());
|
||||||
|
Aircraft = GetCollection<AircraftReadModel>(nameof(Aircraft).Underscore());
|
||||||
|
Airport = GetCollection<AirportReadModel>(nameof(Airport).Underscore());
|
||||||
}
|
}
|
||||||
|
|
||||||
public IMongoCollection<FlightReadModel> Flight { get; }
|
public IMongoCollection<FlightReadModel> Flight { get; }
|
||||||
|
public IMongoCollection<AircraftReadModel> Aircraft { get; }
|
||||||
|
public IMongoCollection<AirportReadModel> Airport { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
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.Events;
|
using Flight.Aircrafts.Events;
|
||||||
|
using Flight.Aircrafts.Features.CreateAircraft.Reads;
|
||||||
using Flight.Airports.Events;
|
using Flight.Airports.Events;
|
||||||
|
using Flight.Airports.Features.CreateAirport.Reads;
|
||||||
using Flight.Flights.Events.Domain;
|
using Flight.Flights.Events.Domain;
|
||||||
|
using Flight.Flights.Features.CreateFlight.Reads;
|
||||||
|
using Flight.Flights.Features.DeleteFlight.Reads;
|
||||||
|
using Flight.Flights.Features.UpdateFlight.Reads;
|
||||||
|
|
||||||
namespace Flight;
|
namespace Flight;
|
||||||
|
|
||||||
// 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
|
||||||
{
|
{
|
||||||
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events) => events.Select(Map);
|
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
|
||||||
|
|
||||||
public IIntegrationEvent Map(IDomainEvent @event)
|
|
||||||
{
|
{
|
||||||
return @event switch
|
return @event switch
|
||||||
{
|
{
|
||||||
@ -26,4 +27,20 @@ public sealed class EventMapper : IEventMapper
|
|||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InternalCommand MapToInternalCommand(IDomainEvent @event)
|
||||||
|
{
|
||||||
|
return @event switch
|
||||||
|
{
|
||||||
|
FlightCreatedDomainEvent e => new CreateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||||
|
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
|
||||||
|
FlightUpdatedDomainEvent e => new UpdateFlightMongoCommand(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId,
|
||||||
|
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,
|
||||||
|
e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted),
|
||||||
|
AircraftCreatedDomainEvent e => new CreateAircraftMongoCommand(e.Id, e.Name, e.Model, e.ManufacturingYear, e.IsDeleted),
|
||||||
|
AirportCreatedDomainEvent e => new CreateAirportMongoCommand(e.Id, e.Name, e.Address, e.Code, e.IsDeleted),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
<Folder Include="Airports\Exceptions" />
|
<Folder Include="Airports\Exceptions" />
|
||||||
<Folder Include="Data\Migrations" />
|
<Folder Include="Data\Migrations" />
|
||||||
<Folder Include="Enum" />
|
<Folder Include="Enum" />
|
||||||
<Folder Include="Flights\Features\UpdateFlight" />
|
|
||||||
<Folder Include="Seats\Features\CreateSeat" />
|
<Folder Include="Seats\Features\CreateSeat" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace Flight.Flights.Features.CreateFlight;
|
|||||||
|
|
||||||
public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId,
|
public record CreateFlightCommand(string FlightNumber, long AircraftId, long DepartureAirportId,
|
||||||
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
DateTime DepartureDate, DateTime ArriveDate, long ArriveAirportId,
|
||||||
decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>
|
decimal DurationMinutes, DateTime FlightDate, FlightStatus Status, decimal Price) : ICommand<FlightResponseDto>, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
public long Id { get; set; } = SnowFlakIdGenerator.NewId();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,10 +44,6 @@ public class CreateFlightCommandHandler : ICommandHandler<CreateFlightCommand, F
|
|||||||
|
|
||||||
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);
|
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);
|
||||||
|
|
||||||
var createFlightMongoCommand = _mapper.Map<CreateFlightMongoCommand>(newFlight.Entity);
|
|
||||||
|
|
||||||
await _persistMessageProcessor.AddInternalMessageAsync(createFlightMongoCommand, cancellationToken);
|
|
||||||
|
|
||||||
return _mapper.Map<FlightResponseDto>(newFlight.Entity);
|
return _mapper.Map<FlightResponseDto>(newFlight.Entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,12 +6,8 @@ namespace Flight.Flights.Features.CreateFlight.Reads;
|
|||||||
|
|
||||||
public class CreateFlightMongoCommand : InternalCommand
|
public class CreateFlightMongoCommand : InternalCommand
|
||||||
{
|
{
|
||||||
public CreateFlightMongoCommand()
|
public CreateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
{
|
long DepartureAirportId,
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public CreateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, long DepartureAirportId,
|
|
||||||
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
||||||
decimal Price, bool IsDeleted)
|
decimal Price, bool IsDeleted)
|
||||||
{
|
{
|
||||||
@ -29,15 +25,15 @@ public class CreateFlightMongoCommand : InternalCommand
|
|||||||
this.IsDeleted = IsDeleted;
|
this.IsDeleted = IsDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string FlightNumber { get; init; }
|
public string FlightNumber { get; }
|
||||||
public long AircraftId { get; init; }
|
public long AircraftId { get; }
|
||||||
public DateTime DepartureDate { get; init; }
|
public DateTime DepartureDate { get; }
|
||||||
public long DepartureAirportId { get; init; }
|
public long DepartureAirportId { get; }
|
||||||
public DateTime ArriveDate { get; init; }
|
public DateTime ArriveDate { get; }
|
||||||
public long ArriveAirportId { get; init; }
|
public long ArriveAirportId { get; }
|
||||||
public decimal DurationMinutes { get; init; }
|
public decimal DurationMinutes { get; }
|
||||||
public DateTime FlightDate { get; init; }
|
public DateTime FlightDate { get; }
|
||||||
public FlightStatus Status { get; init; }
|
public FlightStatus Status { get; }
|
||||||
public decimal Price { get; init; }
|
public decimal Price { get; }
|
||||||
public bool IsDeleted { get; init; }
|
public bool IsDeleted { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Threading;
|
||||||
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;
|
||||||
@ -33,7 +32,7 @@ public class CreateFlightMongoCommandHandler : ICommandHandler<CreateFlightMongo
|
|||||||
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||||
|
|
||||||
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||||
.FirstOrDefaultAsync(x => x.Id == command.Id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == flightReadModel.Id, cancellationToken);
|
||||||
|
|
||||||
if (flight is not null)
|
if (flight is not null)
|
||||||
throw new FlightAlreadyExistException();
|
throw new FlightAlreadyExistException();
|
||||||
|
|||||||
@ -3,4 +3,4 @@ using Flight.Flights.Dtos;
|
|||||||
|
|
||||||
namespace Flight.Flights.Features.DeleteFlight;
|
namespace Flight.Flights.Features.DeleteFlight;
|
||||||
|
|
||||||
public record DeleteFlightCommand(long Id) : ICommand<FlightResponseDto>;
|
public record DeleteFlightCommand(long Id) : ICommand<FlightResponseDto>, IInternalCommand;
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using BuildingBlocks.Core.Event;
|
||||||
|
using Flight.Flights.Models;
|
||||||
|
|
||||||
|
namespace Flight.Flights.Features.DeleteFlight.Reads;
|
||||||
|
|
||||||
|
public class DeleteFlightMongoCommand : InternalCommand
|
||||||
|
{
|
||||||
|
public DeleteFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
|
long DepartureAirportId,
|
||||||
|
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
||||||
|
decimal Price, bool IsDeleted)
|
||||||
|
{
|
||||||
|
this.Id = Id;
|
||||||
|
this.FlightNumber = FlightNumber;
|
||||||
|
this.AircraftId = AircraftId;
|
||||||
|
this.DepartureDate = DepartureDate;
|
||||||
|
this.DepartureAirportId = DepartureAirportId;
|
||||||
|
this.ArriveDate = ArriveDate;
|
||||||
|
this.ArriveAirportId = ArriveAirportId;
|
||||||
|
this.DurationMinutes = DurationMinutes;
|
||||||
|
this.FlightDate = FlightDate;
|
||||||
|
this.Status = Status;
|
||||||
|
this.Price = Price;
|
||||||
|
this.IsDeleted = IsDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FlightNumber { get; }
|
||||||
|
public long AircraftId { get; }
|
||||||
|
public DateTime DepartureDate { get; }
|
||||||
|
public long DepartureAirportId { get; }
|
||||||
|
public DateTime ArriveDate { get; }
|
||||||
|
public long ArriveAirportId { get; }
|
||||||
|
public decimal DurationMinutes { get; }
|
||||||
|
public DateTime FlightDate { get; }
|
||||||
|
public FlightStatus Status { get; }
|
||||||
|
public decimal Price { get; }
|
||||||
|
public bool IsDeleted { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using BuildingBlocks.Core.CQRS;
|
||||||
|
using Flight.Data;
|
||||||
|
using Flight.Flights.Exceptions;
|
||||||
|
using Flight.Flights.Models.Reads;
|
||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace Flight.Flights.Features.DeleteFlight.Reads;
|
||||||
|
|
||||||
|
public class DeleteFlightMongoCommandHandler : ICommandHandler<DeleteFlightMongoCommand>
|
||||||
|
{
|
||||||
|
private readonly FlightReadDbContext _flightReadDbContext;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public DeleteFlightMongoCommandHandler(
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_flightReadDbContext = flightReadDbContext;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(DeleteFlightMongoCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(command, nameof(command));
|
||||||
|
|
||||||
|
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||||
|
|
||||||
|
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||||
|
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId, cancellationToken);
|
||||||
|
|
||||||
|
if (flight is null)
|
||||||
|
throw new FlightNotFountException();
|
||||||
|
|
||||||
|
await _flightReadDbContext.Flight.UpdateOneAsync(
|
||||||
|
x => x.FlightId == flightReadModel.FlightId,
|
||||||
|
Builders<FlightReadModel>.Update
|
||||||
|
.Set(x => x.IsDeleted, flightReadModel.IsDeleted),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,25 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using BuildingBlocks.IdsGenerator;
|
||||||
using Flight.Flights.Dtos;
|
using Flight.Flights.Dtos;
|
||||||
using Flight.Flights.Features.CreateFlight.Reads;
|
using Flight.Flights.Features.CreateFlight.Reads;
|
||||||
|
using Flight.Flights.Features.DeleteFlight.Reads;
|
||||||
|
using Flight.Flights.Features.UpdateFlight.Reads;
|
||||||
using Flight.Flights.Models.Reads;
|
using Flight.Flights.Models.Reads;
|
||||||
using Mapster;
|
using Mapster;
|
||||||
|
|
||||||
namespace Flight.Flights.Features;
|
namespace Flight.Flights.Features;
|
||||||
|
|
||||||
public class FlightMappings : Profile
|
public class FlightMappings : IRegister
|
||||||
{
|
{
|
||||||
public void Register(TypeAdapterConfig config)
|
public void Register(TypeAdapterConfig config)
|
||||||
{
|
{
|
||||||
config.NewConfig<Models.Flight, FlightResponseDto>();
|
config.NewConfig<Models.Flight, FlightResponseDto>();
|
||||||
config.NewConfig<Models.Flight, CreateFlightMongoCommand>();
|
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>()
|
||||||
config.NewConfig<CreateFlightMongoCommand, FlightReadModel>();
|
.Map(d => d.Id, s => SnowFlakIdGenerator.NewId())
|
||||||
|
.Map(d => d.FlightId, s => s.Id);
|
||||||
|
config.NewConfig<UpdateFlightMongoCommand, FlightReadModel>()
|
||||||
|
.Map(d => d.FlightId, s => s.Id);
|
||||||
|
config.NewConfig<DeleteFlightMongoCommand, FlightReadModel>()
|
||||||
|
.Map(d => d.FlightId, s => s.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using BuildingBlocks.Core.Event;
|
||||||
|
using Flight.Flights.Models;
|
||||||
|
|
||||||
|
namespace Flight.Flights.Features.UpdateFlight.Reads;
|
||||||
|
|
||||||
|
public class UpdateFlightMongoCommand : InternalCommand
|
||||||
|
{
|
||||||
|
public UpdateFlightMongoCommand(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate,
|
||||||
|
long DepartureAirportId,
|
||||||
|
DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, FlightStatus Status,
|
||||||
|
decimal Price, bool IsDeleted)
|
||||||
|
{
|
||||||
|
this.Id = Id;
|
||||||
|
this.FlightNumber = FlightNumber;
|
||||||
|
this.AircraftId = AircraftId;
|
||||||
|
this.DepartureDate = DepartureDate;
|
||||||
|
this.DepartureAirportId = DepartureAirportId;
|
||||||
|
this.ArriveDate = ArriveDate;
|
||||||
|
this.ArriveAirportId = ArriveAirportId;
|
||||||
|
this.DurationMinutes = DurationMinutes;
|
||||||
|
this.FlightDate = FlightDate;
|
||||||
|
this.Status = Status;
|
||||||
|
this.Price = Price;
|
||||||
|
this.IsDeleted = IsDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FlightNumber { get; }
|
||||||
|
public long AircraftId { get; }
|
||||||
|
public DateTime DepartureDate { get; }
|
||||||
|
public long DepartureAirportId { get; }
|
||||||
|
public DateTime ArriveDate { get; }
|
||||||
|
public long ArriveAirportId { get; }
|
||||||
|
public decimal DurationMinutes { get; }
|
||||||
|
public DateTime FlightDate { get; }
|
||||||
|
public FlightStatus Status { get; }
|
||||||
|
public decimal Price { get; }
|
||||||
|
public bool IsDeleted { get; }
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
|
using BuildingBlocks.Core.CQRS;
|
||||||
|
using Flight.Data;
|
||||||
|
using Flight.Flights.Exceptions;
|
||||||
|
using Flight.Flights.Models.Reads;
|
||||||
|
using MapsterMapper;
|
||||||
|
using MediatR;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using MongoDB.Driver.Linq;
|
||||||
|
|
||||||
|
namespace Flight.Flights.Features.UpdateFlight.Reads;
|
||||||
|
|
||||||
|
public class UpdateFlightMongoCommandHandler : ICommandHandler<UpdateFlightMongoCommand>
|
||||||
|
{
|
||||||
|
private readonly FlightReadDbContext _flightReadDbContext;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public UpdateFlightMongoCommandHandler(
|
||||||
|
FlightReadDbContext flightReadDbContext,
|
||||||
|
IMapper mapper)
|
||||||
|
{
|
||||||
|
_flightReadDbContext = flightReadDbContext;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Unit> Handle(UpdateFlightMongoCommand command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Guard.Against.Null(command, nameof(command));
|
||||||
|
|
||||||
|
var flightReadModel = _mapper.Map<FlightReadModel>(command);
|
||||||
|
|
||||||
|
var flight = await _flightReadDbContext.Flight.AsQueryable()
|
||||||
|
.FirstOrDefaultAsync(x => x.FlightId == flightReadModel.FlightId, cancellationToken);
|
||||||
|
|
||||||
|
if (flight is null)
|
||||||
|
throw new FlightNotFountException();
|
||||||
|
|
||||||
|
await _flightReadDbContext.Flight.UpdateOneAsync(
|
||||||
|
x => x.FlightId == flightReadModel.FlightId,
|
||||||
|
Builders<FlightReadModel>.Update
|
||||||
|
.Set(x => x.Id, flightReadModel.Id)
|
||||||
|
.Set(x => x.Price, flightReadModel.Price)
|
||||||
|
.Set(x => x.ArriveDate, flightReadModel.ArriveDate)
|
||||||
|
.Set(x => x.AircraftId, flightReadModel.AircraftId)
|
||||||
|
.Set(x => x.DurationMinutes, flightReadModel.DurationMinutes)
|
||||||
|
.Set(x => x.DepartureDate, flightReadModel.DepartureDate)
|
||||||
|
.Set(x => x.FlightDate, flightReadModel.FlightDate)
|
||||||
|
.Set(x => x.FlightId, flightReadModel.FlightId)
|
||||||
|
.Set(x => x.FlightNumber, flightReadModel.FlightNumber)
|
||||||
|
.Set(x => x.IsDeleted, flightReadModel.IsDeleted)
|
||||||
|
.Set(x => x.Status, flightReadModel.Status)
|
||||||
|
.Set(x => x.ArriveAirportId, flightReadModel.ArriveAirportId)
|
||||||
|
.Set(x => x.DepartureAirportId, flightReadModel.DepartureAirportId),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
return Unit.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ using MediatR;
|
|||||||
|
|
||||||
namespace Flight.Flights.Features.UpdateFlight;
|
namespace Flight.Flights.Features.UpdateFlight;
|
||||||
|
|
||||||
public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCacheRequest
|
public record UpdateFlightCommand : ICommand<FlightResponseDto>, IInvalidateCacheRequest, IInternalCommand
|
||||||
{
|
{
|
||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
public string FlightNumber { get; init; }
|
public string FlightNumber { get; init; }
|
||||||
|
|||||||
@ -7,6 +7,7 @@ namespace Flight.Flights.Models.Reads;
|
|||||||
public class FlightReadModel
|
public class FlightReadModel
|
||||||
{
|
{
|
||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
|
public long FlightId { get; set; }
|
||||||
public string FlightNumber { get; init; }
|
public string FlightNumber { get; init; }
|
||||||
public long AircraftId { get; init; }
|
public long AircraftId { get; init; }
|
||||||
public DateTime DepartureDate { get; init; }
|
public DateTime DepartureDate { get; init; }
|
||||||
|
|||||||
@ -63,10 +63,10 @@ app.UseRouting();
|
|||||||
app.UseHttpMetrics();
|
app.UseHttpMetrics();
|
||||||
app.UseProblemDetails();
|
app.UseProblemDetails();
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
app.UseCustomHealthCheck();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
app.UseCustomHealthCheck();
|
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using BuildingBlocks.Core;
|
using BuildingBlocks.Core;
|
||||||
using BuildingBlocks.Core.Event;
|
using BuildingBlocks.Core.Event;
|
||||||
|
|
||||||
@ -7,12 +5,15 @@ namespace Identity;
|
|||||||
|
|
||||||
public sealed class EventMapper : IEventMapper
|
public sealed class EventMapper : IEventMapper
|
||||||
{
|
{
|
||||||
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events)
|
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
|
||||||
{
|
{
|
||||||
return events.Select(Map);
|
return @event switch
|
||||||
|
{
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IIntegrationEvent Map(IDomainEvent @event)
|
public InternalCommand MapToInternalCommand(IDomainEvent @event)
|
||||||
{
|
{
|
||||||
return @event switch
|
return @event switch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -50,7 +50,7 @@ public class RegisterNewUserCommandHandler : ICommandHandler<RegisterNewUserComm
|
|||||||
throw new RegisterIdentityUserException(string.Join(',', roleResult.Errors.Select(e => e.Description)));
|
throw new RegisterIdentityUserException(string.Join(',', roleResult.Errors.Select(e => e.Description)));
|
||||||
|
|
||||||
await _eventDispatcher.SendAsync(new UserCreated(applicationUser.Id, applicationUser.FirstName + " " + applicationUser.LastName,
|
await _eventDispatcher.SendAsync(new UserCreated(applicationUser.Id, applicationUser.FirstName + " " + applicationUser.LastName,
|
||||||
applicationUser.PassPortNumber), cancellationToken);
|
applicationUser.PassPortNumber), cancellationToken: cancellationToken);
|
||||||
|
|
||||||
return new RegisterNewUserResponseDto
|
return new RegisterNewUserResponseDto
|
||||||
{
|
{
|
||||||
|
|||||||
@ -5,12 +5,15 @@ namespace Passenger;
|
|||||||
|
|
||||||
public sealed class EventMapper : IEventMapper
|
public sealed class EventMapper : IEventMapper
|
||||||
{
|
{
|
||||||
public IEnumerable<IIntegrationEvent> MapAll(IEnumerable<IDomainEvent> events)
|
public IIntegrationEvent MapToIntegrationEvent(IDomainEvent @event)
|
||||||
{
|
{
|
||||||
return events.Select(Map);
|
return @event switch
|
||||||
|
{
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IIntegrationEvent Map(IDomainEvent @event)
|
public InternalCommand MapToInternalCommand(IDomainEvent @event)
|
||||||
{
|
{
|
||||||
return @event switch
|
return @event switch
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user