diff --git a/.gitignore b/.gitignore index d6ceb3e..0fceb20 100644 --- a/.gitignore +++ b/.gitignore @@ -435,4 +435,5 @@ fabric.properties *.jwk -**/keys \ No newline at end of file +**/keys +/src/ApiGateway/back.txt diff --git a/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidAircraftIdExceptions.cs b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidAircraftIdExceptions.cs new file mode 100644 index 0000000..2487e37 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidAircraftIdExceptions.cs @@ -0,0 +1,12 @@ +namespace Flight.Aircrafts.Exceptions; +using System; +using BuildingBlocks.Exception; + + +public class InvalidAircraftIdExceptions : BadRequestException +{ + public InvalidAircraftIdExceptions(Guid aircraftId) + : base($"AircraftId: '{aircraftId}' is invalid.") + { + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidManufacturingYearException.cs b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidManufacturingYearException.cs new file mode 100644 index 0000000..da58cce --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidManufacturingYearException.cs @@ -0,0 +1,10 @@ +namespace Flight.Aircrafts.Exceptions; +using BuildingBlocks.Exception; + + +public class InvalidManufacturingYearException : BadRequestException +{ + public InvalidManufacturingYearException() : base("ManufacturingYear must be greater than 1900") + { + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidModelException.cs b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidModelException.cs new file mode 100644 index 0000000..276e733 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidModelException.cs @@ -0,0 +1,10 @@ +namespace Flight.Aircrafts.Exceptions; +using BuildingBlocks.Exception; + + +public class InvalidModelException : BadRequestException +{ + public InvalidModelException() : base("Model cannot be empty or whitespace.") + { + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidNameException.cs b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidNameException.cs new file mode 100644 index 0000000..d2a7354 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Exceptions/InvalidNameException.cs @@ -0,0 +1,10 @@ +namespace Flight.Aircrafts.Exceptions; +using BuildingBlocks.Exception; + + +public class InvalidNameException : BadRequestException +{ + public InvalidNameException() : base("Name cannot be empty or whitespace.") + { + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs index 9776895..deec0ca 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/AircraftMappings.cs @@ -1,9 +1,10 @@ -using Flight.Aircrafts.Models; +using Flight.Aircrafts.Models; using Mapster; namespace Flight.Aircrafts.Features; using CreatingAircraft.V1; +using Flight.Aircrafts.Models.ValueObjects; using MassTransit; public class AircraftMappings : IRegister @@ -11,12 +12,12 @@ public class AircraftMappings : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() - .Map(d => d.Id, s => NewId.NextGuid()) - .Map(d => d.AircraftId, s => s.Id); + .Map(d => d.Id, s => NewId.NextGuid()) + .Map(d => d.AircraftId, s => AircraftId.Of(s.Id)); config.NewConfig() .Map(d => d.Id, s => NewId.NextGuid()) - .Map(d => d.AircraftId, s => s.Id); + .Map(d => d.AircraftId, s => AircraftId.Of(s.Id)); config.NewConfig() .ConstructUsing(x => new CreatingAircraft.V1.CreateAircraft(x.Name, x.Model, x.ManufacturingYear)); diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraft.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraft.cs index 338a638..892df75 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraft.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraft.cs @@ -1,16 +1,17 @@ namespace Flight.Aircrafts.Features.CreatingAircraft.V1; using System; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; using BuildingBlocks.Web; -using Exceptions; -using Models; using Data; using Duende.IdentityServer.EntityFramework.Entities; +using Exceptions; +using Flight.Aircrafts.ValueObjects; using FluentValidation; using MapsterMapper; using MassTransit; @@ -19,6 +20,8 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; +using Models; +using Models.ValueObjects; public record CreateAircraft(string Name, string Model, int ManufacturingYear) : ICommand, IInternalCommand @@ -26,7 +29,7 @@ public record CreateAircraft(string Name, string Model, int ManufacturingYear) : public Guid Id { get; init; } = NewId.NextGuid(); } -public record CreateAircraftResult(Guid Id); +public record CreateAircraftResult(AircraftId Id); public record AircraftCreatedDomainEvent (Guid Id, string Name, string Model, int ManufacturingYear, bool IsDeleted) : IDomainEvent; @@ -88,15 +91,16 @@ internal class CreateAircraftHandler : IRequestHandler x.Model == request.Model, cancellationToken); + var aircraft = await _flightDbContext.Aircraft.AsNoTracking().SingleOrDefaultAsync( + a => a.Model.Value.Equals(Model.Of(request.Model)), cancellationToken); + if (aircraft is not null) { throw new AircraftAlreadyExistException(); } - var aircraftEntity = Aircraft.Create(request.Id, request.Name, request.Model, request.ManufacturingYear); + var aircraftEntity = Aircraft.Create(AircraftId.Of(request.Id), Name.Of(request.Name), Model.Of(request.Model), ManufacturingYear.Of(request.ManufacturingYear)); var newAircraft = (await _flightDbContext.Aircraft.AddAsync(aircraftEntity, cancellationToken))?.Entity; diff --git a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftMongo.cs b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftMongo.cs index 92effba..6ed4b2b 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftMongo.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Features/CreatingAircraft/V1/CreateAircraftMongo.cs @@ -1,4 +1,4 @@ -namespace Flight.Aircrafts.Features.CreatingAircraft.V1; +namespace Flight.Aircrafts.Features.CreatingAircraft.V1; using System; using System.Threading; @@ -6,11 +6,12 @@ using System.Threading.Tasks; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; -using Exceptions; -using Models; using Data; +using Exceptions; using MapsterMapper; using MediatR; +using Models; +using Models.ValueObjects; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -36,7 +37,7 @@ public class CreateAircraftMongoHandler : ICommandHandler var aircraftReadModel = _mapper.Map(request); var aircraft = await _flightReadDbContext.Aircraft.AsQueryable() - .FirstOrDefaultAsync(x => x.AircraftId == aircraftReadModel.AircraftId, cancellationToken); + .FirstOrDefaultAsync(x => x.AircraftId == AircraftId.Of(aircraftReadModel.AircraftId), cancellationToken); if (aircraft is not null) { diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs index 6ac88dd..02ee93f 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/Aircraft.cs @@ -2,16 +2,17 @@ using BuildingBlocks.Core.Model; namespace Flight.Aircrafts.Models; -using System; using Features.CreatingAircraft.V1; +using Flight.Aircrafts.ValueObjects; +using ValueObjects; -public record Aircraft : Aggregate +public record Aircraft : Aggregate { - public string Name { get; private set; } - public string Model { get; private set; } - public int ManufacturingYear { get; private set; } + public Name Name { get; private set; } = default!; + public Model Model { get; private set; } = default!; + public ManufacturingYear ManufacturingYear { get; private set; } = default!; - public static Aircraft Create(Guid id, string name, string model, int manufacturingYear, bool isDeleted = false) + public static Aircraft Create(AircraftId id, Name name, Model model, ManufacturingYear manufacturingYear, bool isDeleted = false) { var aircraft = new Aircraft { diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/AircraftReadModel.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/AircraftReadModel.cs index 3287652..d80052e 100644 --- a/src/Services/Flight/src/Flight/Aircrafts/Models/AircraftReadModel.cs +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/AircraftReadModel.cs @@ -1,4 +1,4 @@ -namespace Flight.Aircrafts.Models; +namespace Flight.Aircrafts.Models; using System; diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/AircraftId.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/AircraftId.cs new file mode 100644 index 0000000..a225b06 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/AircraftId.cs @@ -0,0 +1,28 @@ +namespace Flight.Aircrafts.Models.ValueObjects; +using System; +using Exceptions; + +public record AircraftId +{ + public Guid Value { get; } + + private AircraftId(Guid value) + { + if (value == Guid.Empty) + { + throw new InvalidAircraftIdExceptions(value); + } + + Value = value; + } + + public static AircraftId Of(Guid value) + { + return new AircraftId(value); + } + + public static implicit operator Guid(AircraftId aircraftId) + { + return aircraftId.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/ManufacturingYear.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/ManufacturingYear.cs new file mode 100644 index 0000000..61de900 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/ManufacturingYear.cs @@ -0,0 +1,26 @@ +namespace Flight.Aircrafts.ValueObjects; + +using Flight.Aircrafts.Exceptions; + +public record ManufacturingYear +{ + public int Value { get; } + public ManufacturingYear(int value) + { + if (value < 1900) + { + throw new InvalidManufacturingYearException(); + } + + Value = value; + } + public static ManufacturingYear Of(int value) + { + return new ManufacturingYear(value); + } + + public static implicit operator int(ManufacturingYear manufacturingYear) + { + return manufacturingYear.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Model.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Model.cs new file mode 100644 index 0000000..c7ec562 --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Model.cs @@ -0,0 +1,24 @@ +namespace Flight.Aircrafts.Models.ValueObjects; +using Flight.Aircrafts.Exceptions; + +public record Model +{ + public string Value { get; } + public Model(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidModelException(); + } + Value = value; + } + public static Model Of(string value) + { + return new Model(value); + } + + public static implicit operator string(Model model) + { + return model.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Name.cs b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Name.cs new file mode 100644 index 0000000..91ddcfe --- /dev/null +++ b/src/Services/Flight/src/Flight/Aircrafts/Models/ValueObjects/Name.cs @@ -0,0 +1,24 @@ +namespace Flight.Aircrafts.Models.ValueObjects; +using Exceptions; + +public record Name +{ + public string Value { get; } + public Name(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidNameException(); + } + Value = value; + } + public static Name Of(string value) + { + return new Name(value); + } + + public static implicit operator string(Name name) + { + return name.Value; + } +} diff --git a/src/Services/Flight/src/Flight/Data/Configurations/AircraftConfiguration.cs b/src/Services/Flight/src/Flight/Data/Configurations/AircraftConfiguration.cs index 71fc974..b73a787 100644 --- a/src/Services/Flight/src/Flight/Data/Configurations/AircraftConfiguration.cs +++ b/src/Services/Flight/src/Flight/Data/Configurations/AircraftConfiguration.cs @@ -4,17 +4,53 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Flight.Data.Configurations; +using System; +using Flight.Aircrafts.Models.ValueObjects; + public class AircraftConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable(nameof(Aircraft)); + builder.HasKey(r => r.Id); - builder.Property(r => r.Id).ValueGeneratedNever(); + builder.Property(r => r.Id).ValueGeneratedNever() + .HasConversion(aircraftId => aircraftId.Value, dbId => AircraftId.Of(dbId)); - - // // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api builder.Property(r => r.Version).IsConcurrencyToken(); + + builder.OwnsOne( + x => x.Name, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Aircraft.Name)) + .HasMaxLength(50) + .IsRequired(); + } + ); + + builder.OwnsOne( + x => x.Model, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Aircraft.Model)) + .HasMaxLength(50) + .IsRequired(); + } + ); + + builder.OwnsOne( + x => x.ManufacturingYear, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Aircraft.ManufacturingYear)) + .HasMaxLength(5) + .IsRequired(); + } + ); } } diff --git a/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.Designer.cs b/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.Designer.cs new file mode 100644 index 0000000..55dd254 --- /dev/null +++ b/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.Designer.cs @@ -0,0 +1,363 @@ +// +using System; +using Flight.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Flight.Data.Migrations +{ + [DbContext(typeof(FlightDbContext))] + [Migration("20230525075149_aircraftValueObjects")] + partial class aircraftValueObjects + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_aircraft"); + + b.ToTable("aircraft", (string)null); + }); + + modelBuilder.Entity("Flight.Airports.Models.Airport", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address") + .HasColumnType("text") + .HasColumnName("address"); + + b.Property("Code") + .HasColumnType("text") + .HasColumnName("code"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_airport"); + + b.ToTable("airport", (string)null); + }); + + modelBuilder.Entity("Flight.Flights.Models.Flight", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("aircraft_id"); + + b.Property("ArriveAirportId") + .HasColumnType("uuid") + .HasColumnName("arrive_airport_id"); + + b.Property("ArriveDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("arrive_date"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("DepartureAirportId") + .HasColumnType("uuid") + .HasColumnName("departure_airport_id"); + + b.Property("DepartureDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("departure_date"); + + b.Property("DurationMinutes") + .HasColumnType("numeric") + .HasColumnName("duration_minutes"); + + b.Property("FlightDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("flight_date"); + + b.Property("FlightNumber") + .HasColumnType("text") + .HasColumnName("flight_number"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("Price") + .HasColumnType("numeric") + .HasColumnName("price"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Unknown") + .HasColumnName("status"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_flight"); + + b.HasIndex("AircraftId") + .HasDatabaseName("ix_flight_aircraft_id"); + + b.HasIndex("ArriveAirportId") + .HasDatabaseName("ix_flight_arrive_airport_id"); + + b.ToTable("flight", (string)null); + }); + + modelBuilder.Entity("Flight.Seats.Models.Seat", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Class") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Unknown") + .HasColumnName("class"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("FlightId") + .HasColumnType("uuid") + .HasColumnName("flight_id"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("SeatNumber") + .HasColumnType("text") + .HasColumnName("seat_number"); + + b.Property("Type") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Unknown") + .HasColumnName("type"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_seat"); + + b.HasIndex("FlightId") + .HasDatabaseName("ix_seat_flight_id"); + + b.ToTable("seat", (string)null); + }); + + modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b => + { + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ManufacturingYearValue", "ManufacturingYear", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .HasMaxLength(5) + .HasColumnType("integer") + .HasColumnName("manufacturing_year"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ModelValue", "Model", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("model"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.NameValue", "Name", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.Navigation("ManufacturingYear"); + + b.Navigation("Model"); + + b.Navigation("Name"); + }); + + modelBuilder.Entity("Flight.Flights.Models.Flight", b => + { + b.HasOne("Flight.Aircrafts.Models.Aircraft", null) + .WithMany() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_flight_aircraft_aircraft_id"); + + b.HasOne("Flight.Airports.Models.Airport", null) + .WithMany() + .HasForeignKey("ArriveAirportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_flight_airport_arrive_airport_id"); + }); + + modelBuilder.Entity("Flight.Seats.Models.Seat", b => + { + b.HasOne("Flight.Flights.Models.Flight", null) + .WithMany() + .HasForeignKey("FlightId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_seat_flight_flight_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.cs b/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.cs new file mode 100644 index 0000000..c08d4ca --- /dev/null +++ b/src/Services/Flight/src/Flight/Data/Migrations/20230525075149_aircraftValueObjects.cs @@ -0,0 +1,120 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Flight.Data.Migrations +{ + /// + public partial class aircraftValueObjects : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_flight_aircraft_aircraft_id", + table: "flight"); + + migrationBuilder.AlterColumn( + name: "aircraft_id", + table: "flight", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "name", + table: "aircraft", + type: "character varying(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "model", + table: "aircraft", + type: "character varying(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "manufacturing_year", + table: "aircraft", + type: "integer", + maxLength: 5, + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AddForeignKey( + name: "fk_flight_aircraft_aircraft_id", + table: "flight", + column: "aircraft_id", + principalTable: "aircraft", + principalColumn: "id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_flight_aircraft_aircraft_id", + table: "flight"); + + migrationBuilder.AlterColumn( + name: "aircraft_id", + table: "flight", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "name", + table: "aircraft", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(50)", + oldMaxLength: 50, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "model", + table: "aircraft", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(50)", + oldMaxLength: 50, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "manufacturing_year", + table: "aircraft", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldMaxLength: 5, + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "fk_flight_aircraft_aircraft_id", + table: "flight", + column: "aircraft_id", + principalTable: "aircraft", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs b/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs index 5df1ffd..1ea716d 100644 --- a/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs +++ b/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs @@ -48,18 +48,6 @@ namespace Flight.Data.Migrations .HasColumnType("bigint") .HasColumnName("last_modified_by"); - b.Property("ManufacturingYear") - .HasColumnType("integer") - .HasColumnName("manufacturing_year"); - - b.Property("Model") - .HasColumnType("text") - .HasColumnName("model"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - b.Property("Version") .IsConcurrencyToken() .HasColumnType("bigint") @@ -126,7 +114,7 @@ namespace Flight.Data.Migrations .HasColumnType("uuid") .HasColumnName("id"); - b.Property("AircraftId") + b.Property("AircraftId") .HasColumnType("uuid") .HasColumnName("aircraft_id"); @@ -268,13 +256,85 @@ namespace Flight.Data.Migrations b.ToTable("seat", (string)null); }); + modelBuilder.Entity("Flight.Aircrafts.Models.Aircraft", b => + { + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ManufacturingYearValue", "ManufacturingYear", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .HasMaxLength(5) + .HasColumnType("integer") + .HasColumnName("manufacturing_year"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.ModelValue", "Model", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("model"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.OwnsOne("Flight.Aircrafts.Models.ValueObjects.NameValue", "Name", b1 => + { + b1.Property("AircraftId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b1.HasKey("AircraftId") + .HasName("pk_aircraft"); + + b1.ToTable("aircraft"); + + b1.WithOwner() + .HasForeignKey("AircraftId") + .HasConstraintName("fk_aircraft_aircraft_id"); + }); + + b.Navigation("ManufacturingYear"); + + b.Navigation("Model"); + + b.Navigation("Name"); + }); + modelBuilder.Entity("Flight.Flights.Models.Flight", b => { b.HasOne("Flight.Aircrafts.Models.Aircraft", null) .WithMany() .HasForeignKey("AircraftId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() .HasConstraintName("fk_flight_aircraft_aircraft_id"); b.HasOne("Flight.Airports.Models.Airport", null) diff --git a/src/Services/Flight/src/Flight/Data/Seed/InitialData.cs b/src/Services/Flight/src/Flight/Data/Seed/InitialData.cs index 2953dd7..f4b2887 100644 --- a/src/Services/Flight/src/Flight/Data/Seed/InitialData.cs +++ b/src/Services/Flight/src/Flight/Data/Seed/InitialData.cs @@ -1,19 +1,21 @@ -namespace Flight.Data.Seed; +namespace Flight.Data.Seed; using System; using System.Collections.Generic; using System.Linq; using Aircrafts.Models; +using Aircrafts.Models.ValueObjects; using Airports.Models; +using Flight.Aircrafts.ValueObjects; using Flights.Models; using MassTransit; using Seats.Models; public static class InitialData { - public static List Airports { get;} - public static List Aircrafts { get;} - public static List Seats { get;} + public static List Airports { get; } + public static List Aircrafts { get; } + public static List Seats { get; } public static List Flights { get; } @@ -27,9 +29,9 @@ public static class InitialData Aircrafts = new List { - Aircraft.Create(new Guid("3c5c0000-97c6-fc34-fcd3-08db322230c8"), "Boeing 737", "B737", 2005), - Aircraft.Create(new Guid("3c5c0000-97c6-fc34-2e04-08db322230c9"), "Airbus 300", "A300", 2000), - Aircraft.Create(new Guid("3c5c0000-97c6-fc34-2e11-08db322230c9"), "Airbus 320", "A320", 2003) + Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-fcd3-08db322230c8")), Name.Of("Boeing 737"), Model.Of("B737"), ManufacturingYear.Of(2005)), + Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e04-08db322230c9")), Name.Of("Airbus 300"), Model.Of("A300"), ManufacturingYear.Of(2000)), + Aircraft.Create(AircraftId.Of(new Guid("3c5c0000-97c6-fc34-2e11-08db322230c9")), Name.Of("Airbus 320"), Model.Of("A320"), ManufacturingYear.Of(2003)) }; diff --git a/src/Services/Flight/src/Flight/EventMapper.cs b/src/Services/Flight/src/Flight/EventMapper.cs index d367bb6..cd85a5c 100644 --- a/src/Services/Flight/src/Flight/EventMapper.cs +++ b/src/Services/Flight/src/Flight/EventMapper.cs @@ -5,6 +5,7 @@ using BuildingBlocks.Core.Event; namespace Flight; using Aircrafts.Features.CreatingAircraft.V1; +using Aircrafts.Models.ValueObjects; using Airports.Features.CreatingAirport.V1; using Flights.Features.CreatingFlight.V1; using Flights.Features.DeletingFlight.V1; @@ -34,7 +35,7 @@ public sealed class EventMapper : IEventMapper { return @event switch { - FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, e.AircraftId, e.DepartureDate, e.DepartureAirportId, + FlightCreatedDomainEvent e => new CreateFlightMongo(e.Id, e.FlightNumber, AircraftId.Of(e.AircraftId), e.DepartureDate, e.DepartureAirportId, e.ArriveDate, e.ArriveAirportId, e.DurationMinutes, e.FlightDate, e.Status, e.Price, e.IsDeleted), 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), diff --git a/src/Services/Flight/src/Flight/Flights/Dtos/FlightDto.cs b/src/Services/Flight/src/Flight/Flights/Dtos/FlightDto.cs index 9610506..1da222e 100644 --- a/src/Services/Flight/src/Flight/Flights/Dtos/FlightDto.cs +++ b/src/Services/Flight/src/Flight/Flights/Dtos/FlightDto.cs @@ -1,7 +1,6 @@ using System; namespace Flight.Flights.Dtos; - public record FlightDto(Guid Id, string FlightNumber, Guid AircraftId, Guid DepartureAirportId, DateTime DepartureDate, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, DateTime FlightDate, Enums.FlightStatus Status, decimal Price); diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs index 14c53f0..bfa825d 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreatingFlight/V1/CreateFlight.cs @@ -3,6 +3,7 @@ namespace Flight.Flights.Features.CreatingFlight.V1; using System; using System.Threading; using System.Threading.Tasks; +using Aircrafts.Models.ValueObjects; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; @@ -111,7 +112,7 @@ internal class CreateFlightHandler : ICommandHandler() - .ConstructUsing(x => new FlightDto(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)); config.NewConfig() diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlight.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlight.cs index ea7633b..4358191 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlight.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdatingFlight/V1/UpdateFlight.cs @@ -3,6 +3,7 @@ namespace Flight.Flights.Features.UpdatingFlight.V1; using System; using System.Threading; using System.Threading.Tasks; +using Aircrafts.Models.ValueObjects; using Ardalis.GuardClauses; using BuildingBlocks.Caching; using BuildingBlocks.Core.CQRS; @@ -110,7 +111,7 @@ internal class UpdateFlightHandler : ICommandHandler { public string FlightNumber { get; private set; } - public Guid AircraftId { get; private set; } + public AircraftId AircraftId { get; private set; } public DateTime DepartureDate { get; private set; } public Guid DepartureAirportId { get; private set; } public DateTime ArriveDate { get; private set; } @@ -20,7 +21,7 @@ public record Flight : Aggregate public Enums.FlightStatus Status { get; private set; } public decimal Price { get; private set; } - public static Flight Create(Guid id, string flightNumber, Guid aircraftId, + public static Flight Create(Guid id, string flightNumber, AircraftId aircraftId, Guid departureAirportId, DateTime departureDate, DateTime arriveDate, Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status, decimal price, bool isDeleted = false) @@ -52,7 +53,7 @@ public record Flight : Aggregate } - public void Update(Guid id, string flightNumber, Guid aircraftId, + public void Update(Guid id, string flightNumber, AircraftId aircraftId, Guid departureAirportId, DateTime departureDate, DateTime arriveDate, Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status, decimal price, bool isDeleted = false) @@ -75,7 +76,7 @@ public record Flight : Aggregate AddDomainEvent(@event); } - public void Delete(Guid id, string flightNumber, Guid aircraftId, + public void Delete(Guid id, string flightNumber, AircraftId aircraftId, Guid departureAirportId, DateTime departureDate, DateTime arriveDate, Guid arriveAirportId, decimal durationMinutes, DateTime flightDate, Enums.FlightStatus status, decimal price, bool isDeleted = true) diff --git a/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs b/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs index 352c9a7..7077139 100644 --- a/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Aircraft/Features/CreateAircraftTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.TestBase; using Flight.Api; @@ -26,7 +26,7 @@ public class CreateAircraftTests : FlightIntegrationTestBase var response = await Fixture.SendAsync(command); // Assert - response?.Id.Should().Be(command.Id); + response?.Id.Value.Should().Be(command.Id); (await Fixture.WaitForPublishing()).Should().Be(true); } diff --git a/src/Services/Flight/tests/IntegrationTest/Fakes/FakeCreateAircraftCommand.cs b/src/Services/Flight/tests/IntegrationTest/Fakes/FakeCreateAircraftCommand.cs index 530326f..646ced6 100644 --- a/src/Services/Flight/tests/IntegrationTest/Fakes/FakeCreateAircraftCommand.cs +++ b/src/Services/Flight/tests/IntegrationTest/Fakes/FakeCreateAircraftCommand.cs @@ -1,4 +1,4 @@ -using AutoBogus; +using AutoBogus; namespace Integration.Test.Fakes; @@ -10,5 +10,6 @@ public class FakeCreateAircraftCommand : AutoFaker public FakeCreateAircraftCommand() { RuleFor(r => r.Id, _ => NewId.NextGuid()); + RuleFor(r => r.ManufacturingYear, _ => 2000); } } diff --git a/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs b/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs index de15c88..a00335c 100644 --- a/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs +++ b/src/Services/Flight/tests/UnitTest/Common/DbContextFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Flight.Data; using Flight.Flights.Enums; @@ -7,6 +7,7 @@ using Microsoft.EntityFrameworkCore; namespace Unit.Test.Common; +using global::Flight.Aircrafts.Models.ValueObjects; using MassTransit; public static class DbContextFactory @@ -45,16 +46,16 @@ public static class DbContextFactory var aircrafts = new List { - global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft1, "Boeing 737", "B737", 2005), - global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft2, "Airbus 300", "A300", 2000), - global::Flight.Aircrafts.Models.Aircraft.Create(_aircraft3, "Airbus 320", "A320", 2003) + global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft1), Name.Of("Boeing 737"), Model.Of("B737"), ManufacturingYear.Of(2005)), + global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft2), Name.Of("Airbus 300"), Model.Of("A300"), ManufacturingYear.Of(2000)), + global::Flight.Aircrafts.Models.Aircraft.Create(AircraftId.Of(_aircraft3), Name.Of("Airbus 320"), Model.Of("A320"), ManufacturingYear.Of(2003)) }; context.Aircraft.AddRange(aircrafts); var flights = new List { - global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", _aircraft1, _airportId1, + global::Flight.Flights.Models.Flight.Create(_flightId1, "BD467", AircraftId.Of(_aircraft1), _airportId1, new DateTime(2022, 1, 31, 12, 0, 0), new DateTime(2022, 1, 31, 14, 0, 0), _airportId2, 120m, diff --git a/src/Services/Flight/tests/UnitTest/Fakes/FakeCreateAircraftCommand.cs b/src/Services/Flight/tests/UnitTest/Fakes/FakeCreateAircraftCommand.cs index 913c900..34d91ef 100644 --- a/src/Services/Flight/tests/UnitTest/Fakes/FakeCreateAircraftCommand.cs +++ b/src/Services/Flight/tests/UnitTest/Fakes/FakeCreateAircraftCommand.cs @@ -1,4 +1,4 @@ -using AutoBogus; +using AutoBogus; namespace Unit.Test.Fakes; diff --git a/src/Services/Flight/tests/UnitTest/Fakes/FakeFlightCreate.cs b/src/Services/Flight/tests/UnitTest/Fakes/FakeFlightCreate.cs index 5d4660b..767b2fa 100644 --- a/src/Services/Flight/tests/UnitTest/Fakes/FakeFlightCreate.cs +++ b/src/Services/Flight/tests/UnitTest/Fakes/FakeFlightCreate.cs @@ -1,4 +1,6 @@ -namespace Unit.Test.Fakes; +namespace Unit.Test.Fakes; + +using global::Flight.Aircrafts.Models.ValueObjects; public static class FakeFlightCreate { @@ -7,7 +9,7 @@ public static class FakeFlightCreate var command = new FakeCreateFlightCommand().Generate(); return global::Flight.Flights.Models.Flight.Create(command.Id, command.FlightNumber, - command.AircraftId, command.DepartureAirportId, command.DepartureDate, + AircraftId.Of(command.AircraftId), command.DepartureAirportId, command.DepartureDate, command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, command.Status, command.Price); } diff --git a/src/Services/Flight/tests/UnitTest/Flight/FlightMappingTests.cs b/src/Services/Flight/tests/UnitTest/Flight/FlightMappingTests.cs index 495ec0b..922c805 100644 --- a/src/Services/Flight/tests/UnitTest/Flight/FlightMappingTests.cs +++ b/src/Services/Flight/tests/UnitTest/Flight/FlightMappingTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Flight.Flights.Dtos; using MapsterMapper; @@ -25,7 +25,8 @@ public class FlightMappingTests yield return new object[] { // these types will instantiate with reflection in the future - typeof(global::Flight.Flights.Models.Flight), typeof(FlightDto) + //typeof(global::Flight.Flights.Models.Flight), typeof(FlightDto), + typeof(global::Flight.Flights.Models.FlightReadModel), typeof(FlightDto) }; } } diff --git a/src/Services/Passenger/src/Passenger/Data/Configurations/PassengerConfiguration.cs b/src/Services/Passenger/src/Passenger/Data/Configurations/PassengerConfiguration.cs index 9bfeff0..58117a8 100644 --- a/src/Services/Passenger/src/Passenger/Data/Configurations/PassengerConfiguration.cs +++ b/src/Services/Passenger/src/Passenger/Data/Configurations/PassengerConfiguration.cs @@ -3,16 +3,58 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Passenger.Data.Configurations; -public class PassengerConfiguration: IEntityTypeConfiguration +using Passengers.Models.ValueObjects; + +public class PassengerConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - builder.ToTable(nameof(Passenger)); + builder.ToTable(nameof(Passengers.Models.Passenger)); builder.HasKey(r => r.Id); - builder.Property(r => r.Id).ValueGeneratedNever(); + builder.Property(r => r.Id).ValueGeneratedNever() + .HasConversion(passengerId => passengerId.Value, dbId => PassengerId.Of(dbId)); - // // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api builder.Property(r => r.Version).IsConcurrencyToken(); + + builder.OwnsOne( + x => x.Name, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Passengers.Models.Passenger.Name)) + .HasMaxLength(50) + .IsRequired(); + } + ); + + builder.OwnsOne( + x => x.PassportNumber, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Passengers.Models.Passenger.PassportNumber)) + .HasMaxLength(10) + .IsRequired(); + } + ); + + builder.OwnsOne( + x => x.Age, + a => + { + a.Property(p => p.Value) + .HasColumnName(nameof(Passengers.Models.Passenger.Age)) + .HasMaxLength(3) + .IsRequired(); + } + ); + + builder.Property(x => x.PassengerType) + .IsRequired() + .HasDefaultValue(Passengers.Enums.PassengerType.Unknown) + .HasConversion( + x => x.ToString(), + x => (Passengers.Enums.PassengerType)Enum.Parse(typeof(Passengers.Enums.PassengerType), x)); } } diff --git a/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.Designer.cs b/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.Designer.cs new file mode 100644 index 0000000..f5359cc --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.Designer.cs @@ -0,0 +1,148 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Passenger.Data; + +#nullable disable + +namespace Passenger.Data.Migrations +{ + [DbContext(typeof(PassengerDbContext))] + [Migration("20230524195049_AddValueObject")] + partial class AddValueObject + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp without time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("PassengerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Unknown") + .HasColumnName("passenger_type"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_passenger"); + + b.ToTable("passenger", (string)null); + }); + + modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b => + { + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .HasMaxLength(3) + .HasColumnType("integer") + .HasColumnName("age"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("passport_number"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.Navigation("Age"); + + b.Navigation("Name"); + + b.Navigation("PassportNumber"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.cs b/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.cs new file mode 100644 index 0000000..95f7654 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Data/Migrations/20230524195049_AddValueObject.cs @@ -0,0 +1,133 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Passenger.Data.Migrations +{ + /// + public partial class AddValueObject : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "passport_number", + table: "passenger", + type: "character varying(10)", + maxLength: 10, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "passenger_type", + table: "passenger", + type: "text", + nullable: false, + defaultValue: "Unknown", + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "name", + table: "passenger", + type: "character varying(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "last_modified", + table: "passenger", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_at", + table: "passenger", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "age", + table: "passenger", + type: "integer", + maxLength: 3, + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "passport_number", + table: "passenger", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(10)", + oldMaxLength: 10, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "passenger_type", + table: "passenger", + type: "integer", + nullable: false, + oldClrType: typeof(string), + oldType: "text", + oldDefaultValue: "Unknown"); + + migrationBuilder.AlterColumn( + name: "name", + table: "passenger", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(50)", + oldMaxLength: 50, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "last_modified", + table: "passenger", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_at", + table: "passenger", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "age", + table: "passenger", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldMaxLength: 3, + oldNullable: true); + } + } +} diff --git a/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.Designer.cs b/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.Designer.cs new file mode 100644 index 0000000..83dc119 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.Designer.cs @@ -0,0 +1,148 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Passenger.Data; + +#nullable disable + +namespace Passenger.Data.Migrations +{ + [DbContext(typeof(PassengerDbContext))] + [Migration("20230526085126_change-behavior")] + partial class changebehavior + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("bigint") + .HasColumnName("created_by"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasColumnName("is_deleted"); + + b.Property("LastModified") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_modified"); + + b.Property("LastModifiedBy") + .HasColumnType("bigint") + .HasColumnName("last_modified_by"); + + b.Property("PassengerType") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Unknown") + .HasColumnName("passenger_type"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_passenger"); + + b.ToTable("passenger", (string)null); + }); + + modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b => + { + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .HasMaxLength(3) + .HasColumnType("integer") + .HasColumnName("age"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("passport_number"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.Navigation("Age"); + + b.Navigation("Name"); + + b.Navigation("PassportNumber"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.cs b/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.cs new file mode 100644 index 0000000..954e4f6 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Data/Migrations/20230526085126_change-behavior.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Passenger.Data.Migrations +{ + /// + public partial class changebehavior : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "last_modified", + table: "passenger", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_at", + table: "passenger", + type: "timestamp with time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone", + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "last_modified", + table: "passenger", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_at", + table: "passenger", + type: "timestamp without time zone", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone", + oldNullable: true); + } + } +} diff --git a/src/Services/Passenger/src/Passenger/Data/Migrations/PassengerDbContextModelSnapshot.cs b/src/Services/Passenger/src/Passenger/Data/Migrations/PassengerDbContextModelSnapshot.cs index 2614a28..96a7468 100644 --- a/src/Services/Passenger/src/Passenger/Data/Migrations/PassengerDbContextModelSnapshot.cs +++ b/src/Services/Passenger/src/Passenger/Data/Migrations/PassengerDbContextModelSnapshot.cs @@ -28,10 +28,6 @@ namespace Passenger.Data.Migrations .HasColumnType("uuid") .HasColumnName("id"); - b.Property("Age") - .HasColumnType("integer") - .HasColumnName("age"); - b.Property("CreatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("created_at"); @@ -52,18 +48,13 @@ namespace Passenger.Data.Migrations .HasColumnType("bigint") .HasColumnName("last_modified_by"); - b.Property("Name") + b.Property("PassengerType") + .IsRequired() + .ValueGeneratedOnAdd() .HasColumnType("text") - .HasColumnName("name"); - - b.Property("PassengerType") - .HasColumnType("integer") + .HasDefaultValue("Unknown") .HasColumnName("passenger_type"); - b.Property("PassportNumber") - .HasColumnType("text") - .HasColumnName("passport_number"); - b.Property("Version") .IsConcurrencyToken() .HasColumnType("bigint") @@ -74,6 +65,80 @@ namespace Passenger.Data.Migrations b.ToTable("passenger", (string)null); }); + + modelBuilder.Entity("Passenger.Passengers.Models.Passenger", b => + { + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Age", "Age", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .HasMaxLength(3) + .HasColumnType("integer") + .HasColumnName("age"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.Name", "Name", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("name"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.OwnsOne("Passenger.Passengers.Models.ValueObjects.PassportNumber", "PassportNumber", b1 => + { + b1.Property("PassengerId") + .HasColumnType("uuid") + .HasColumnName("id"); + + b1.Property("Value") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("passport_number"); + + b1.HasKey("PassengerId") + .HasName("pk_passenger"); + + b1.ToTable("passenger"); + + b1.WithOwner() + .HasForeignKey("PassengerId") + .HasConstraintName("fk_passenger_passenger_id"); + }); + + b.Navigation("Age"); + + b.Navigation("Name"); + + b.Navigation("PassportNumber"); + }); #pragma warning restore 612, 618 } } diff --git a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs index 82cccb0..2aa1227 100644 --- a/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs +++ b/src/Services/Passenger/src/Passenger/Data/PassengerDbContext.cs @@ -1,7 +1,7 @@ using System.Reflection; using BuildingBlocks.EFCore; -using Microsoft.EntityFrameworkCore; using BuildingBlocks.Web; +using Microsoft.EntityFrameworkCore; namespace Passenger.Data; diff --git a/src/Services/Passenger/src/Passenger/Exceptions/InvalidPassengerIdExceptions.cs b/src/Services/Passenger/src/Passenger/Exceptions/InvalidPassengerIdExceptions.cs new file mode 100644 index 0000000..c35ae6d --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Exceptions/InvalidPassengerIdExceptions.cs @@ -0,0 +1,12 @@ +namespace Passenger.Exceptions; +using System; + +using BuildingBlocks.Exception; + +public class InvalidPassengerIdExceptions : BadRequestException +{ + public InvalidPassengerIdExceptions(Guid passengerId) + : base($"PassengerId: '{passengerId}' is invalid.") + { + } +} diff --git a/src/Services/Passenger/src/Passenger/Identity/Consumers/RegisteringNewUser/V1/RegisterNewUser.cs b/src/Services/Passenger/src/Passenger/Identity/Consumers/RegisteringNewUser/V1/RegisterNewUser.cs index 27456a8..4818b80 100644 --- a/src/Services/Passenger/src/Passenger/Identity/Consumers/RegisteringNewUser/V1/RegisterNewUser.cs +++ b/src/Services/Passenger/src/Passenger/Identity/Consumers/RegisteringNewUser/V1/RegisterNewUser.cs @@ -5,12 +5,13 @@ using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.Core; using BuildingBlocks.Core.Event; using BuildingBlocks.Web; +using Data; using Humanizer; using MassTransit; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Data; +using Passenger.Passengers.Models.ValueObjects; public class RegisterNewUserHandler : IConsumer { @@ -37,15 +38,15 @@ public class RegisterNewUserHandler : IConsumer _logger.LogInformation($"consumer for {nameof(UserCreated).Underscore()} in {_options.Name}"); var passengerExist = - await _passengerDbContext.Passengers.AnyAsync(x => x.PassportNumber == context.Message.PassportNumber); + await _passengerDbContext.Passengers.AnyAsync(x => x.PassportNumber.Value == PassportNumber.Of(context.Message.PassportNumber).Value); if (passengerExist) { return; } - var passenger = Passengers.Models.Passenger.Create(NewId.NextGuid(), context.Message.Name, - context.Message.PassportNumber); + var passenger = Passengers.Models.Passenger.Create(PassengerId.Of(NewId.NextGuid()), Name.Of(context.Message.Name), + PassportNumber.Of(context.Message.PassportNumber)); await _passengerDbContext.AddAsync(passenger); diff --git a/src/Services/Passenger/src/Passenger/Passengers/Dtos/PassengerDto.cs b/src/Services/Passenger/src/Passenger/Passengers/Dtos/PassengerDto.cs index 12b61cd..17fad5c 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Dtos/PassengerDto.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Dtos/PassengerDto.cs @@ -1,3 +1,2 @@ namespace Passenger.Passengers.Dtos; - public record PassengerDto(Guid Id, string Name, string PassportNumber, Enums.PassengerType PassengerType, int Age); diff --git a/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidAgeException.cs b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidAgeException.cs new file mode 100644 index 0000000..df9a826 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidAgeException.cs @@ -0,0 +1,9 @@ +namespace Passenger.Passengers.Exceptions; +using BuildingBlocks.Exception; + +public class InvalidAgeException : BadRequestException +{ + public InvalidAgeException() : base("Age Cannot be null or negative") + { + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidNameException.cs b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidNameException.cs new file mode 100644 index 0000000..0a44de6 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidNameException.cs @@ -0,0 +1,10 @@ +namespace Passenger.Passengers.Exceptions; +using BuildingBlocks.Exception; + + +public class InvalidNameException : BadRequestException +{ + public InvalidNameException() : base("Name cannot be empty or whitespace.") + { + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidPassportNumberException.cs b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidPassportNumberException.cs new file mode 100644 index 0000000..bb6429b --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Exceptions/InvalidPassportNumberException.cs @@ -0,0 +1,10 @@ +namespace Passenger.Passengers.Exceptions; +using BuildingBlocks.Exception; + + +public class InvalidPassportNumberException : BadRequestException +{ + public InvalidPassportNumberException() : base("Passport number cannot be empty or whitespace.") + { + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassenger.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassenger.cs index d6bdf12..308ae89 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassenger.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassenger.cs @@ -4,18 +4,20 @@ using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; using BuildingBlocks.Web; -using Exceptions; -using FluentValidation; -using MapsterMapper; -using Microsoft.EntityFrameworkCore; using Data; using Dtos; using Duende.IdentityServer.EntityFramework.Entities; +using Exceptions; +using FluentValidation; +using MapsterMapper; using MassTransit; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; +using Passenger.Passengers.Models.ValueObjects; +using Passenger.Passengers.ValueObjects; public record CompleteRegisterPassenger (string PassportNumber, Enums.PassengerType PassengerType, int Age) : ICommand, @@ -96,7 +98,8 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler x.PassportNumber == request.PassportNumber, cancellationToken); + x => x.PassportNumber.Value.Equals(PassportNumber.Of(request.PassportNumber).Value), cancellationToken); + if (passenger is null) { @@ -104,11 +107,11 @@ internal class CompleteRegisterPassengerCommandHandler : ICommandHandler(updatePassenger.Entity); + var passengerDto = _mapper.Map(updatePassenger); return new CompleteRegisterPassengerResult(passengerDto); } diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerMongo.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerMongo.cs index 0402a00..f858cc4 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerMongo.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/CompletingRegisterPassenger/V1/CompleteRegisterPassengerMongo.cs @@ -1,14 +1,15 @@ -namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1; +namespace Passenger.Passengers.Features.CompletingRegisterPassenger.V1; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; +using Data; using MapsterMapper; using MediatR; using Models; +using Models.ValueObjects; using MongoDB.Driver; using MongoDB.Driver.Linq; -using Data; public record CompleteRegisterPassengerMongoCommand(Guid Id, string PassportNumber, string Name, Enums.PassengerType PassengerType, int Age, bool IsDeleted = false) : InternalCommand; @@ -34,14 +35,14 @@ internal class CompleteRegisterPassengerMongoHandler : ICommandHandler(request); var passenger = await _passengerReadDbContext.Passenger.AsQueryable() - .FirstOrDefaultAsync(x => x.PassengerId == passengerReadModel.PassengerId && !x.IsDeleted, cancellationToken); + .FirstOrDefaultAsync(x => x.PassengerId == PassengerId.Of(passengerReadModel.PassengerId) && !x.IsDeleted, cancellationToken); if (passenger is not null) { await _passengerReadDbContext.Passenger.UpdateOneAsync( - x => x.PassengerId == passengerReadModel.PassengerId, + x => x.PassengerId == PassengerId.Of(passengerReadModel.PassengerId), Builders.Update - .Set(x => x.PassengerId, passengerReadModel.PassengerId) + .Set(x => x.PassengerId, PassengerId.Of(passengerReadModel.PassengerId)) .Set(x => x.Age, passengerReadModel.Age) .Set(x => x.Name, passengerReadModel.Name) .Set(x => x.IsDeleted, passengerReadModel.IsDeleted) diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerById.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerById.cs index bfe3428..911108c 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerById.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/GettingPassengerById/Queries/V1/GetPassengerById.cs @@ -1,18 +1,19 @@ namespace Passenger.Passengers.Features.GettingPassengerById.Queries.V1; +using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; +using BuildingBlocks.Web; using Data; using Dtos; -using FluentValidation; -using MapsterMapper; -using Ardalis.GuardClauses; -using BuildingBlocks.Web; using Duende.IdentityServer.EntityFramework.Entities; using Exceptions; +using FluentValidation; +using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; +using Models.ValueObjects; using MongoDB.Driver; using MongoDB.Driver.Linq; @@ -74,7 +75,7 @@ internal class GetPassengerByIdHandler : IQueryHandler x.PassengerId == query.Id && x.IsDeleted == false, cancellationToken); + .SingleOrDefaultAsync(x => x.PassengerId == PassengerId.Of(query.Id) && x.IsDeleted == false, cancellationToken); if (passenger is null) { diff --git a/src/Services/Passenger/src/Passenger/Passengers/Features/PassengerMappings.cs b/src/Services/Passenger/src/Passenger/Passengers/Features/PassengerMappings.cs index 2e59b3c..4a953d3 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Features/PassengerMappings.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Features/PassengerMappings.cs @@ -6,19 +6,23 @@ using CompletingRegisterPassenger.V1; using Dtos; using MassTransit; using Models; +using Passenger.Passengers.Models.ValueObjects; public class PassengerMappings : IRegister { public void Register(TypeAdapterConfig config) { config.NewConfig() - .Map(d => d.Id, s => NewId.NextGuid()) - .Map(d => d.PassengerId, s => s.Id); + .Map(d => d.Id, s => NewId.NextGuid()) + .Map(d => d.PassengerId, s => PassengerId.Of(s.Id)); config.NewConfig() .ConstructUsing(x => new CompleteRegisterPassenger(x.PassportNumber, x.PassengerType, x.Age)); config.NewConfig() .ConstructUsing(x => new PassengerDto(x.PassengerId, x.Name, x.PassportNumber, x.PassengerType, x.Age)); + + config.NewConfig() + .ConstructUsing(x => new PassengerDto(x.Id.Value, x.Name.Value, x.PassportNumber.Value, x.PassengerType, x.Age.Value)); } } diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/Passenger.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/Passenger.cs index 638218e..d5dac75 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Models/Passenger.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/Passenger.cs @@ -3,24 +3,31 @@ using BuildingBlocks.Core.Model; namespace Passenger.Passengers.Models; using Features.CompletingRegisterPassenger.V1; +using global::Passenger.Passengers.Models.ValueObjects; +using global::Passenger.Passengers.ValueObjects; using Identity.Consumers.RegisteringNewUser.V1; -public record Passenger : Aggregate +public record Passenger : Aggregate { - public Passenger CompleteRegistrationPassenger(Guid id, string name, string passportNumber, Enums.PassengerType passengerType, int age, bool isDeleted = false) + public PassportNumber PassportNumber { get; private set; } = default!; + public Name Name { get; private set; } = default!; + public Enums.PassengerType PassengerType { get; private set; } + public Age? Age { get; private set; } + + public Passenger CompleteRegistrationPassenger(PassengerId id, Name name, PassportNumber passportNumber, Enums.PassengerType passengerType, Age age, bool isDeleted = false) { var passenger = new Passenger { + Id = id, Name = name, PassportNumber = passportNumber, PassengerType = passengerType, Age = age, - Id = id, IsDeleted = isDeleted }; var @event = new PassengerRegistrationCompletedDomainEvent(passenger.Id, passenger.Name, passenger.PassportNumber, - passenger.PassengerType, passenger.Age, passenger.IsDeleted); + passenger.PassengerType, passenger.Age.Value, passenger.IsDeleted); passenger.AddDomainEvent(@event); @@ -28,7 +35,7 @@ public record Passenger : Aggregate } - public static Passenger Create(Guid id, string name, string passportNumber, bool isDeleted = false) + public static Passenger Create(PassengerId id, Name name, PassportNumber passportNumber, bool isDeleted = false) { var passenger = new Passenger { Id = id, Name = name, PassportNumber = passportNumber, IsDeleted = isDeleted }; @@ -38,10 +45,4 @@ public record Passenger : Aggregate return passenger; } - - - public string PassportNumber { get; private set; } - public string Name { get; private set; } - public Enums.PassengerType PassengerType { get; private set; } - public int Age { get; private set; } } diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/PassengerReadModel.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/PassengerReadModel.cs index 8225399..faa64a3 100644 --- a/src/Services/Passenger/src/Passenger/Passengers/Models/PassengerReadModel.cs +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/PassengerReadModel.cs @@ -1,5 +1,4 @@ -namespace Passenger.Passengers.Models; - +namespace Passenger.Passengers.Models; public class PassengerReadModel { public required Guid Id { get; init; } diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Age.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Age.cs new file mode 100644 index 0000000..7c43262 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Age.cs @@ -0,0 +1,26 @@ +namespace Passenger.Passengers.ValueObjects; + +using Passenger.Passengers.Exceptions; + +public record Age +{ + public int Value { get; } + public Age(int value) + { + if (value <= 0) + { + throw new InvalidAgeException(); + } + + Value = value; + } + public static Age Of(int value) + { + return new Age(value); + } + + public static implicit operator int(Age age) + { + return age.Value; + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Name.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Name.cs new file mode 100644 index 0000000..a137285 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/Name.cs @@ -0,0 +1,24 @@ +namespace Passenger.Passengers.Models.ValueObjects; +using Exceptions; + +public record Name +{ + public string Value { get; } + public Name(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidNameException(); + } + Value = value; + } + public static Name Of(string value) + { + return new Name(value); + } + + public static implicit operator string(Name name) + { + return name.Value; + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassengerId.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassengerId.cs new file mode 100644 index 0000000..c3cedfa --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassengerId.cs @@ -0,0 +1,28 @@ +namespace Passenger.Passengers.Models.ValueObjects; +using System; +using global::Passenger.Exceptions; + +public record PassengerId +{ + public Guid Value { get; } + + private PassengerId(Guid value) + { + if (value == Guid.Empty) + { + throw new InvalidPassengerIdExceptions(value); + } + + Value = value; + } + + public static PassengerId Of(Guid value) + { + return new PassengerId(value); + } + + public static implicit operator Guid(PassengerId passengerId) + { + return passengerId.Value; + } +} diff --git a/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassportNumber.cs b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassportNumber.cs new file mode 100644 index 0000000..0cc0834 --- /dev/null +++ b/src/Services/Passenger/src/Passenger/Passengers/Models/ValueObjects/PassportNumber.cs @@ -0,0 +1,32 @@ +namespace Passenger.Passengers.Models.ValueObjects; +using Exceptions; + +public record PassportNumber +{ + public string Value { get; } + + public override string ToString() + { + return Value; + } + + private PassportNumber(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new InvalidPassportNumberException(); + } + Value = value; + } + + public static PassportNumber Of(string value) + { + return new PassportNumber(value); + } + + public static implicit operator string(PassportNumber passportNumber) + { + return passportNumber.Value; + } +} + diff --git a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs index 7d5c436..ecc88af 100644 --- a/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs +++ b/src/Services/Passenger/tests/IntegrationTest/Passenger/Features/CompleteRegisterPassengerTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.TestBase; using FluentAssertions; @@ -8,9 +8,9 @@ using Passenger.Data; using Xunit; namespace Integration.Test.Passenger.Features; - public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase { + public CompleteRegisterPassengerTests( TestFixture integrationTestFactory) : base(integrationTestFactory) { @@ -23,8 +23,8 @@ public class CompleteRegisterPassengerTests : PassengerIntegrationTestBase var userCreated = new FakeUserCreated().Generate(); await Fixture.Publish(userCreated); - await Fixture.WaitForPublishing(); - await Fixture.WaitForConsuming(); + (await Fixture.WaitForPublishing()).Should().Be(true); + (await Fixture.WaitForConsuming()).Should().Be(true); var command = new FakeCompleteRegisterPassengerCommand(userCreated.PassportNumber).Generate();