From 3887e7bc44d838784129d89bf6c4fe4ec82b7b2c Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:20:50 +0300 Subject: [PATCH 1/8] Add address/order handling --- src/Imprink.Application/IUnitOfWork.cs | 2 +- .../Mappings/ProductMappingProfile.cs | 11 +- src/Imprink.Domain/Entities/Address.cs | 13 +- src/Imprink.Domain/Entities/Order.cs | 15 +- src/Imprink.Domain/Entities/OrderAddress.cs | 12 +- src/Imprink.Domain/Entities/OrderItem.cs | 17 -- src/Imprink.Domain/Entities/Product.cs | 2 +- src/Imprink.Domain/Entities/ProductVariant.cs | 2 +- src/Imprink.Domain/Entities/User.cs | 5 +- .../Repositories/IAddressRepository.cs | 22 ++ .../Repositories/IOrderItemRepository.cs | 27 -- .../Repositories/IOrderRepository.cs | 26 +- .../Configuration/AddressConfiguration.cs | 37 ++- .../OrderAddressConfiguration.cs | 33 +- .../Configuration/OrderConfiguration.cs | 187 ++++++++---- .../Configuration/OrderItemConfiguration.cs | 64 ---- .../Configuration/UserConfiguration.cs | 5 +- .../Database/ApplicationDbContext.cs | 2 - .../Repositories/AddressRepository.cs | 168 ++++++++++ .../Repositories/OrderItemRepository.cs | 204 ------------- .../Repositories/OrderRepository.cs | 288 +++++++----------- src/Imprink.Infrastructure/UnitOfWork.cs | 4 +- .../StartupApplicationExtensions.cs | 2 +- .../CreateCategoryHandlerIntegrationTest.cs | 2 +- 24 files changed, 562 insertions(+), 588 deletions(-) delete mode 100644 src/Imprink.Domain/Entities/OrderItem.cs create mode 100644 src/Imprink.Domain/Repositories/IAddressRepository.cs delete mode 100644 src/Imprink.Domain/Repositories/IOrderItemRepository.cs delete mode 100644 src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs create mode 100644 src/Imprink.Infrastructure/Repositories/AddressRepository.cs delete mode 100644 src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs diff --git a/src/Imprink.Application/IUnitOfWork.cs b/src/Imprink.Application/IUnitOfWork.cs index a3346a2..4655022 100644 --- a/src/Imprink.Application/IUnitOfWork.cs +++ b/src/Imprink.Application/IUnitOfWork.cs @@ -11,7 +11,7 @@ public interface IUnitOfWork public IUserRoleRepository UserRoleRepository { get; } public IRoleRepository RoleRepository { get; } public IOrderRepository OrderRepository { get; } - public IOrderItemRepository OrderItemRepository { get; } + public IAddressRepository AddressRepository { get; } Task SaveAsync(CancellationToken cancellationToken = default); Task BeginTransactionAsync(CancellationToken cancellationToken = default); diff --git a/src/Imprink.Application/Mappings/ProductMappingProfile.cs b/src/Imprink.Application/Mappings/ProductMappingProfile.cs index 389651f..ba17cc0 100644 --- a/src/Imprink.Application/Mappings/ProductMappingProfile.cs +++ b/src/Imprink.Application/Mappings/ProductMappingProfile.cs @@ -16,17 +16,15 @@ public class ProductMappingProfile: Profile .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) - .ForMember(dest => dest.Product, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.Product, opt => opt.Ignore()); CreateMap(); CreateMap() .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) - .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()); CreateMap(); - + CreateMap() .ForMember(dest => dest.Id, opt => opt.Ignore()) .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) @@ -34,8 +32,7 @@ public class ProductMappingProfile: Profile .ForMember(dest => dest.CreatedBy, opt => opt.Ignore()) .ForMember(dest => dest.ModifiedBy, opt => opt.Ignore()) .ForMember(dest => dest.Category, opt => opt.Ignore()) - .ForMember(dest => dest.ProductVariants, opt => opt.Ignore()) - .ForMember(dest => dest.OrderItems, opt => opt.Ignore()); + .ForMember(dest => dest.ProductVariants, opt => opt.Ignore()); CreateMap(); } diff --git a/src/Imprink.Domain/Entities/Address.cs b/src/Imprink.Domain/Entities/Address.cs index 857f331..42e592b 100644 --- a/src/Imprink.Domain/Entities/Address.cs +++ b/src/Imprink.Domain/Entities/Address.cs @@ -4,11 +4,22 @@ public class Address : EntityBase { public required string UserId { get; set; } public required string AddressType { get; set; } - public required string Street { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public required string AddressLine1 { get; set; } + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } public required string City { get; set; } public required string State { get; set; } public required string PostalCode { get; set; } public required string Country { get; set; } + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } public required bool IsDefault { get; set; } public required bool IsActive { get; set; } + + public virtual User User { get; set; } = null!; } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/Order.cs b/src/Imprink.Domain/Entities/Order.cs index 66d5e68..144d8b3 100644 --- a/src/Imprink.Domain/Entities/Order.cs +++ b/src/Imprink.Domain/Entities/Order.cs @@ -4,15 +4,24 @@ public class Order : EntityBase { public string UserId { get; set; } = null!; public DateTime OrderDate { get; set; } - public decimal TotalPrice { get; set; } + public decimal Amount { get; set; } + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid? ProductVariantId { get; set; } public int OrderStatusId { get; set; } public int ShippingStatusId { get; set; } public string OrderNumber { get; set; } = null!; - public string Notes { get; set; } = null!; + public string? Notes { get; set; } + public string? MerchantId { get; set; } + public string? ComposingImageUrl { get; set; } + public string[] OriginalImageUrls { get; set; } = []; + public string CustomizationImageUrl { get; set; } = null!; + public string CustomizationDescription { get; set; } = null!; public OrderStatus OrderStatus { get; set; } = null!; public User User { get; set; } = null!; public ShippingStatus ShippingStatus { get; set; } = null!; public OrderAddress OrderAddress { get; set; } = null!; - public virtual ICollection OrderItems { get; set; } = new List(); + public Product Product { get; set; } = null!; + public ProductVariant? ProductVariant { get; set; } } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderAddress.cs b/src/Imprink.Domain/Entities/OrderAddress.cs index 1505f65..944fe37 100644 --- a/src/Imprink.Domain/Entities/OrderAddress.cs +++ b/src/Imprink.Domain/Entities/OrderAddress.cs @@ -3,11 +3,21 @@ namespace Imprink.Domain.Entities; public class OrderAddress : EntityBase { public Guid OrderId { get; set; } - public required string Street { get; set; } + public required string AddressType { get; set; } + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public required string AddressLine1 { get; set; } + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } public required string City { get; set; } public required string State { get; set; } public required string PostalCode { get; set; } public required string Country { get; set; } + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } public virtual required Order Order { get; set; } } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderItem.cs b/src/Imprink.Domain/Entities/OrderItem.cs deleted file mode 100644 index 6d8b769..0000000 --- a/src/Imprink.Domain/Entities/OrderItem.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Imprink.Domain.Entities; - -public class OrderItem : EntityBase -{ - public Guid OrderId { get; set; } - public Guid ProductId { get; set; } - public Guid? ProductVariantId { get; set; } - public int Quantity { get; set; } - public decimal UnitPrice { get; set; } - public decimal TotalPrice { get; set; } - public string CustomizationImageUrl { get; set; } = null!; - public string CustomizationDescription { get; set; } = null!; - - public Order Order { get; set; } = null!; - public Product Product { get; set; } = null!; - public ProductVariant ProductVariant { get; set; } = null!; -} \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/Product.cs b/src/Imprink.Domain/Entities/Product.cs index d640552..1476817 100644 --- a/src/Imprink.Domain/Entities/Product.cs +++ b/src/Imprink.Domain/Entities/Product.cs @@ -12,5 +12,5 @@ public class Product : EntityBase public virtual required Category Category { get; set; } public virtual ICollection ProductVariants { get; set; } = new List(); - public virtual ICollection OrderItems { get; set; } = new List(); + public virtual ICollection Orders { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/ProductVariant.cs b/src/Imprink.Domain/Entities/ProductVariant.cs index e9fee5b..4829f98 100644 --- a/src/Imprink.Domain/Entities/ProductVariant.cs +++ b/src/Imprink.Domain/Entities/ProductVariant.cs @@ -12,5 +12,5 @@ public class ProductVariant : EntityBase public bool IsActive { get; set; } public virtual required Product Product { get; set; } - public virtual ICollection OrderItems { get; set; } = new List(); + public virtual ICollection Orders { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/User.cs b/src/Imprink.Domain/Entities/User.cs index 59edbfa..3bfef13 100644 --- a/src/Imprink.Domain/Entities/User.cs +++ b/src/Imprink.Domain/Entities/User.cs @@ -8,14 +8,15 @@ public class User public required string Email { get; set; } public bool EmailVerified { get; set; } - public string? FirstName { get; set; } = null!; - public string? LastName { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } public string? PhoneNumber { get; set; } public required bool IsActive { get; set; } public virtual ICollection
Addresses { get; set; } = new List
(); public virtual ICollection UserRoles { get; set; } = new List(); public virtual ICollection Orders { get; set; } = new List(); + public virtual ICollection MerchantOrders { get; set; } = new List(); public Address? DefaultAddress => Addresses.FirstOrDefault(a => a is { IsDefault: true, IsActive: true }); public IEnumerable Roles => UserRoles.Select(ur => ur.Role); diff --git a/src/Imprink.Domain/Repositories/IAddressRepository.cs b/src/Imprink.Domain/Repositories/IAddressRepository.cs new file mode 100644 index 0000000..911061d --- /dev/null +++ b/src/Imprink.Domain/Repositories/IAddressRepository.cs @@ -0,0 +1,22 @@ +using Imprink.Domain.Entities; + +namespace Imprink.Domain.Repositories; + +public interface IAddressRepository +{ + Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default); + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); + Task
AddAsync(Address address, CancellationToken cancellationToken = default); + Task
UpdateAsync(Address address, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteByUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); + Task SetDefaultAddressAsync(string userId, Guid addressId, CancellationToken cancellationToken = default); + Task DeactivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); + Task ActivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); + Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); + Task IsUserAddressAsync(Guid id, string userId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IOrderItemRepository.cs b/src/Imprink.Domain/Repositories/IOrderItemRepository.cs deleted file mode 100644 index 7f8d123..0000000 --- a/src/Imprink.Domain/Repositories/IOrderItemRepository.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Imprink.Domain.Entities; - -namespace Imprink.Domain.Repositories; - -public interface IOrderItemRepository -{ - Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithProductAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default); - Task> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task> GetByOrderIdWithProductsAsync(Guid orderId, CancellationToken cancellationToken = default); - Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default); - Task> GetByProductVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default); - Task> GetCustomizedItemsAsync(CancellationToken cancellationToken = default); - Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); - Task AddAsync(OrderItem orderItem, CancellationToken cancellationToken = default); - Task> AddRangeAsync(IEnumerable orderItems, CancellationToken cancellationToken = default); - Task UpdateAsync(OrderItem orderItem, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); - Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); - Task GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default); - Task GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default); - Task> GetProductSalesCountAsync(DateTime? startDate = null, DateTime? endDate = null, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IOrderRepository.cs b/src/Imprink.Domain/Repositories/IOrderRepository.cs index 0f53fba..d5a95fb 100644 --- a/src/Imprink.Domain/Repositories/IOrderRepository.cs +++ b/src/Imprink.Domain/Repositories/IOrderRepository.cs @@ -6,22 +6,24 @@ namespace Imprink.Domain.Repositories; public interface IOrderRepository { Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default); Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default); - Task> GetPagedAsync(OrderFilterParameters filterParameters, CancellationToken cancellationToken = default); Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); - Task> GetByUserIdPagedAsync(string userId, int pageNumber, int pageSize, CancellationToken cancellationToken = default); - Task> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default); - Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default); + Task> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default); + Task> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default); + Task> GetByMerchantIdWithDetailsAsync(string merchantId, CancellationToken cancellationToken = default); Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); + Task> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default); + Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default); Task AddAsync(Order order, CancellationToken cancellationToken = default); Task UpdateAsync(Order order, CancellationToken cancellationToken = default); - Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default); - Task GetTotalRevenueAsync(CancellationToken cancellationToken = default); - Task GetTotalRevenueByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default); - Task GetOrderCountByStatusAsync(int orderStatusId, CancellationToken cancellationToken = default); + Task IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default); + Task IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default); + Task GenerateOrderNumberAsync(CancellationToken cancellationToken = default); + Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default); + Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default); + Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default); + Task UnassignMerchantAsync(Guid orderId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs index d1e4bbd..6a1bcce 100644 --- a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs @@ -17,10 +17,31 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.Property(a => a.AddressType) .IsRequired() .HasMaxLength(50); + + builder.Property(a => a.FirstName) + .HasMaxLength(100); + + builder.Property(a => a.LastName) + .HasMaxLength(100); + + builder.Property(a => a.Company) + .HasMaxLength(200); - builder.Property(a => a.Street) + builder.Property(a => a.AddressLine1) .IsRequired() .HasMaxLength(200); + + builder.Property(a => a.AddressLine2) + .HasMaxLength(200); + + builder.Property(a => a.ApartmentNumber) + .HasMaxLength(20); + + builder.Property(a => a.BuildingNumber) + .HasMaxLength(20); + + builder.Property(a => a.Floor) + .HasMaxLength(20); builder.Property(a => a.City) .IsRequired() @@ -36,6 +57,12 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.Property(a => a.Country) .IsRequired() .HasMaxLength(100); + + builder.Property(a => a.PhoneNumber) + .HasMaxLength(20); + + builder.Property(a => a.Instructions) + .HasMaxLength(500); builder.Property(a => a.IsDefault) .IsRequired() @@ -45,6 +72,12 @@ public class AddressConfiguration : EntityBaseConfiguration
.IsRequired() .HasDefaultValue(true); + builder.HasOne(a => a.User) + .WithMany(u => u.Addresses) + .HasForeignKey(a => a.UserId) + .HasPrincipalKey(u => u.Id) + .OnDelete(DeleteBehavior.Cascade); + builder.HasIndex(a => a.UserId) .HasDatabaseName("IX_Address_UserId"); @@ -54,4 +87,4 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.HasIndex(a => new { a.UserId, a.IsDefault }) .HasDatabaseName("IX_Address_User_Default"); } -} \ No newline at end of file +} diff --git a/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs index 0fe6e64..c701bc7 100644 --- a/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/OrderAddressConfiguration.cs @@ -12,10 +12,35 @@ public class OrderAddressConfiguration : EntityBaseConfiguration builder.Property(oa => oa.OrderId) .IsRequired(); + + builder.Property(oa => oa.AddressType) + .IsRequired() + .HasMaxLength(50); + + builder.Property(oa => oa.FirstName) + .HasMaxLength(100); + + builder.Property(oa => oa.LastName) + .HasMaxLength(100); + + builder.Property(oa => oa.Company) + .HasMaxLength(200); - builder.Property(oa => oa.Street) + builder.Property(oa => oa.AddressLine1) .IsRequired() .HasMaxLength(200); + + builder.Property(oa => oa.AddressLine2) + .HasMaxLength(200); + + builder.Property(oa => oa.ApartmentNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.BuildingNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.Floor) + .HasMaxLength(20); builder.Property(oa => oa.City) .IsRequired() @@ -32,6 +57,12 @@ public class OrderAddressConfiguration : EntityBaseConfiguration .IsRequired() .HasMaxLength(100); + builder.Property(oa => oa.PhoneNumber) + .HasMaxLength(20); + + builder.Property(oa => oa.Instructions) + .HasMaxLength(500); + builder.HasIndex(oa => oa.OrderId) .IsUnique() .HasDatabaseName("IX_OrderAddress_OrderId"); diff --git a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs index e4a827b..0aebad5 100644 --- a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using Imprink.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -5,73 +6,131 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Imprink.Infrastructure.Configuration; public class OrderConfiguration : EntityBaseConfiguration +{ + public override void Configure(EntityTypeBuilder builder) { - public override void Configure(EntityTypeBuilder builder) - { - base.Configure(builder); + base.Configure(builder); + + builder.Property(o => o.UserId) + .IsRequired() + .HasMaxLength(450); + + builder.Property(o => o.OrderDate) + .IsRequired(); - builder.Property(o => o.UserId) - .IsRequired() - .HasMaxLength(450); + builder.Property(o => o.Amount) + .IsRequired() + .HasColumnType("decimal(18,2)"); - builder.Property(o => o.OrderDate) - .IsRequired(); - - builder.Property(o => o.TotalPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(o => o.OrderStatusId) - .IsRequired(); - - builder.Property(o => o.ShippingStatusId) - .IsRequired(); - - builder.Property(o => o.OrderNumber) - .IsRequired() - .HasMaxLength(50); - - builder.Property(o => o.Notes) - .HasMaxLength(1000); + builder.Property(o => o.Quantity) + .IsRequired() + .HasDefaultValue(1); - builder.HasOne(o => o.OrderStatus) - .WithMany(os => os.Orders) - .HasForeignKey(o => o.OrderStatusId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(o => o.ShippingStatus) - .WithMany(ss => ss.Orders) - .HasForeignKey(o => o.ShippingStatusId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(o => o.OrderAddress) - .WithOne(oa => oa.Order) - .HasForeignKey(oa => oa.OrderId) - .OnDelete(DeleteBehavior.Cascade); + builder.Property(o => o.ProductId) + .IsRequired(); - builder.HasOne(o => o.User) - .WithMany(u => u.Orders) - .HasForeignKey(o => o.UserId) - .HasPrincipalKey(u => u.Id) - .OnDelete(DeleteBehavior.Restrict); + builder.Property(o => o.OrderStatusId) + .IsRequired(); + + builder.Property(o => o.ShippingStatusId) + .IsRequired(); + + builder.Property(o => o.OrderNumber) + .IsRequired() + .HasMaxLength(50); + + builder.Property(o => o.Notes) + .HasMaxLength(1000); - builder.HasIndex(o => o.UserId) - .HasDatabaseName("IX_Order_UserId"); - - builder.HasIndex(o => o.OrderNumber) - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - builder.HasIndex(o => o.OrderDate) - .HasDatabaseName("IX_Order_OrderDate"); - - builder.HasIndex(o => o.OrderStatusId) - .HasDatabaseName("IX_Order_OrderStatusId"); - - builder.HasIndex(o => o.ShippingStatusId) - .HasDatabaseName("IX_Order_ShippingStatusId"); - - builder.HasIndex(o => new { o.UserId, o.OrderDate }) - .HasDatabaseName("IX_Order_User_Date"); - } - } \ No newline at end of file + builder.Property(o => o.MerchantId) + .HasMaxLength(450); + + builder.Property(o => o.ComposingImageUrl) + .HasMaxLength(1000); + + builder.Property(o => o.OriginalImageUrls) + .HasConversion( + v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), + v => JsonSerializer.Deserialize(v, (JsonSerializerOptions?)null) ?? Array.Empty()) + .HasColumnType("nvarchar(max)"); + + builder.Property(o => o.CustomizationImageUrl) + .IsRequired() + .HasMaxLength(1000); + + builder.Property(o => o.CustomizationDescription) + .IsRequired() + .HasMaxLength(2000); + + builder.HasOne(o => o.OrderStatus) + .WithMany(os => os.Orders) + .HasForeignKey(o => o.OrderStatusId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.ShippingStatus) + .WithMany(ss => ss.Orders) + .HasForeignKey(o => o.ShippingStatusId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.OrderAddress) + .WithOne(oa => oa.Order) + .HasForeignKey(oa => oa.OrderId) + .OnDelete(DeleteBehavior.Cascade); + + builder.HasOne(o => o.User) + .WithMany(u => u.Orders) + .HasForeignKey(o => o.UserId) + .HasPrincipalKey(u => u.Id) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne() + .WithMany(u => u.MerchantOrders) + .HasForeignKey(o => o.MerchantId) + .HasPrincipalKey(u => u.Id) + .OnDelete(DeleteBehavior.SetNull); + + builder.HasOne(o => o.Product) + .WithMany(p => p.Orders) + .HasForeignKey(o => o.ProductId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(o => o.ProductVariant) + .WithMany(pv => pv.Orders) + .HasForeignKey(o => o.ProductVariantId) + .OnDelete(DeleteBehavior.Restrict); + + builder.HasIndex(o => o.UserId) + .HasDatabaseName("IX_Order_UserId"); + + builder.HasIndex(o => o.OrderNumber) + .IsUnique() + .HasDatabaseName("IX_Order_OrderNumber"); + + builder.HasIndex(o => o.OrderDate) + .HasDatabaseName("IX_Order_OrderDate"); + + builder.HasIndex(o => o.OrderStatusId) + .HasDatabaseName("IX_Order_OrderStatusId"); + + builder.HasIndex(o => o.ShippingStatusId) + .HasDatabaseName("IX_Order_ShippingStatusId"); + + builder.HasIndex(o => o.MerchantId) + .HasDatabaseName("IX_Order_MerchantId"); + + builder.HasIndex(o => o.ProductId) + .HasDatabaseName("IX_Order_ProductId"); + + builder.HasIndex(o => o.ProductVariantId) + .HasDatabaseName("IX_Order_ProductVariantId"); + + builder.HasIndex(o => new { o.UserId, o.OrderDate }) + .HasDatabaseName("IX_Order_User_Date"); + + builder.HasIndex(o => new { o.MerchantId, o.OrderDate }) + .HasDatabaseName("IX_Order_Merchant_Date"); + + builder.HasIndex(o => new { o.ProductId, o.OrderDate }) + .HasDatabaseName("IX_Order_Product_Date"); + } +} diff --git a/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs deleted file mode 100644 index e95654e..0000000 --- a/src/Imprink.Infrastructure/Configuration/OrderItemConfiguration.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Imprink.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Imprink.Infrastructure.Configuration; - -public class OrderItemConfiguration : EntityBaseConfiguration - { - public override void Configure(EntityTypeBuilder builder) - { - base.Configure(builder); - - builder.Property(oi => oi.OrderId) - .IsRequired(); - - builder.Property(oi => oi.ProductId) - .IsRequired(); - - builder.Property(oi => oi.Quantity) - .IsRequired() - .HasDefaultValue(1); - - builder.Property(oi => oi.UnitPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(oi => oi.TotalPrice) - .IsRequired() - .HasColumnType("decimal(18,2)"); - - builder.Property(oi => oi.CustomizationImageUrl) - .HasMaxLength(500); - - builder.Property(oi => oi.CustomizationDescription) - .HasMaxLength(2000); - - builder.HasOne(oi => oi.Order) - .WithMany(o => o.OrderItems) - .HasForeignKey(oi => oi.OrderId) - .OnDelete(DeleteBehavior.Cascade); - - builder.HasOne(oi => oi.Product) - .WithMany(p => p.OrderItems) - .HasForeignKey(oi => oi.ProductId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasOne(oi => oi.ProductVariant) - .WithMany(pv => pv.OrderItems) - .HasForeignKey(oi => oi.ProductVariantId) - .OnDelete(DeleteBehavior.Restrict); - - builder.HasIndex(oi => oi.OrderId) - .HasDatabaseName("IX_OrderItem_OrderId"); - - builder.HasIndex(oi => oi.ProductId) - .HasDatabaseName("IX_OrderItem_ProductId"); - - builder.HasIndex(oi => oi.ProductVariantId) - .HasDatabaseName("IX_OrderItem_ProductVariantId"); - - builder.HasIndex(oi => new { oi.OrderId, oi.ProductId }) - .HasDatabaseName("IX_OrderItem_Order_Product"); - } - } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs index 15b71bc..3725f6c 100644 --- a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs @@ -23,10 +23,9 @@ public class UserConfiguration : IEntityTypeConfiguration builder.Property(u => u.Nickname) .IsRequired() .HasMaxLength(100); - + builder.Property(u => u.EmailVerified) - .IsRequired() - .HasMaxLength(100); + .IsRequired(); builder.Property(u => u.FirstName) .HasMaxLength(100); diff --git a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs index 844fe62..423bef6 100644 --- a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs +++ b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs @@ -11,7 +11,6 @@ public class ApplicationDbContext(DbContextOptions options public DbSet Products { get; set; } public DbSet ProductVariants { get; set; } public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } public DbSet OrderAddresses { get; set; } public DbSet
Addresses { get; set; } public DbSet OrderStatuses { get; set; } @@ -28,7 +27,6 @@ public class ApplicationDbContext(DbContextOptions options modelBuilder.ApplyConfiguration(new ProductConfiguration()); modelBuilder.ApplyConfiguration(new ProductVariantConfiguration()); modelBuilder.ApplyConfiguration(new OrderConfiguration()); - modelBuilder.ApplyConfiguration(new OrderItemConfiguration()); modelBuilder.ApplyConfiguration(new OrderAddressConfiguration()); modelBuilder.ApplyConfiguration(new AddressConfiguration()); modelBuilder.ApplyConfiguration(new OrderStatusConfiguration()); diff --git a/src/Imprink.Infrastructure/Repositories/AddressRepository.cs b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs new file mode 100644 index 0000000..e30e420 --- /dev/null +++ b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs @@ -0,0 +1,168 @@ +using Imprink.Domain.Entities; +using Imprink.Domain.Repositories; +using Imprink.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace Imprink.Infrastructure.Repositories; + +public class AddressRepository(ApplicationDbContext context) : IAddressRepository +{ + public async Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId) + .OrderByDescending(a => a.IsDefault) + .ThenBy(a => a.AddressType) + .ToListAsync(cancellationToken); + } + + public async Task> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId && a.IsActive) + .OrderByDescending(a => a.IsDefault) + .ThenBy(a => a.AddressType) + .ToListAsync(cancellationToken); + } + + public async Task GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.UserId == userId && a.IsDefault && a.IsActive, cancellationToken); + } + + public async Task> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default) + { + return await context.Addresses + .Where(a => a.UserId == userId && a.AddressType == addressType && a.IsActive) + .OrderByDescending(a => a.IsDefault) + .ToListAsync(cancellationToken); + } + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id, cancellationToken); + } + + public async Task GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + } + + public async Task
AddAsync(Address address, CancellationToken cancellationToken = default) + { + if (address.IsDefault) + { + await UnsetDefaultAddressesAsync(address.UserId, cancellationToken); + } + + context.Addresses.Add(address); + return address; + } + + public async Task
UpdateAsync(Address address, CancellationToken cancellationToken = default) + { + var existingAddress = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == address.Id, cancellationToken); + + if (existingAddress == null) + throw new InvalidOperationException($"Address with ID {address.Id} not found"); + + if (address.IsDefault && !existingAddress.IsDefault) + { + await UnsetDefaultAddressesAsync(address.UserId, cancellationToken); + } + + context.Entry(existingAddress).CurrentValues.SetValues(address); + return existingAddress; + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id, cancellationToken); + + if (address == null) + return false; + + context.Addresses.Remove(address); + return true; + } + + public async Task DeleteByUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + + if (address == null) + return false; + + context.Addresses.Remove(address); + return true; + } + + public async Task SetDefaultAddressAsync(string userId, Guid addressId, CancellationToken cancellationToken = default) + { + await UnsetDefaultAddressesAsync(userId, cancellationToken); + + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId && a.UserId == userId, cancellationToken); + + if (address != null) + { + address.IsDefault = true; + } + } + + public async Task DeactivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken); + + if (address != null) + { + address.IsActive = false; + if (address.IsDefault) + { + address.IsDefault = false; + } + } + } + + public async Task ActivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default) + { + var address = await context.Addresses + .FirstOrDefaultAsync(a => a.Id == addressId, cancellationToken); + + if (address != null) + { + address.IsActive = true; + } + } + + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.Addresses + .AnyAsync(a => a.Id == id, cancellationToken); + } + + public async Task IsUserAddressAsync(Guid id, string userId, CancellationToken cancellationToken = default) + { + return await context.Addresses + .AnyAsync(a => a.Id == id && a.UserId == userId, cancellationToken); + } + + private async Task UnsetDefaultAddressesAsync(string userId, CancellationToken cancellationToken = default) + { + var defaultAddresses = await context.Addresses + .Where(a => a.UserId == userId && a.IsDefault) + .ToListAsync(cancellationToken); + + foreach (var addr in defaultAddresses) + { + addr.IsDefault = false; + } + } +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs deleted file mode 100644 index 916e457..0000000 --- a/src/Imprink.Infrastructure/Repositories/OrderItemRepository.cs +++ /dev/null @@ -1,204 +0,0 @@ -using Imprink.Domain.Entities; -using Imprink.Domain.Repositories; -using Imprink.Infrastructure.Database; -using Microsoft.EntityFrameworkCore; - -namespace Imprink.Infrastructure.Repositories; - -public class OrderItemRepository(ApplicationDbContext context) : IOrderItemRepository -{ - public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdWithProductAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdWithVariantAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.ProductVariant) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .ThenInclude(o => o.User) - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(oi => oi.ProductVariant) - .FirstOrDefaultAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task> GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .OrderBy(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByOrderIdWithProductsAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(oi => oi.ProductVariant) - .Where(oi => oi.OrderId == orderId) - .OrderBy(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Where(oi => oi.ProductId == productId) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByProductVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Where(oi => oi.ProductVariantId == productVariantId) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetCustomizedItemsAsync(CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Product) - .Include(oi => oi.Order) - .Where(oi => !string.IsNullOrEmpty(oi.CustomizationImageUrl) || !string.IsNullOrEmpty(oi.CustomizationDescription)) - .OrderByDescending(oi => oi.CreatedAt) - .ToListAsync(cancellationToken); - } - - public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Include(oi => oi.Order) - .Include(oi => oi.Product) - .Where(oi => oi.Order.OrderDate >= startDate && oi.Order.OrderDate <= endDate) - .OrderByDescending(oi => oi.Order.OrderDate) - .ToListAsync(cancellationToken); - } - - public Task AddAsync(OrderItem orderItem, CancellationToken cancellationToken = default) - { - orderItem.Id = Guid.NewGuid(); - orderItem.CreatedAt = DateTime.UtcNow; - orderItem.ModifiedAt = DateTime.UtcNow; - - context.OrderItems.Add(orderItem); - return Task.FromResult(orderItem); - } - - public Task> AddRangeAsync(IEnumerable orderItems, CancellationToken cancellationToken = default) - { - var items = orderItems.ToList(); - var utcNow = DateTime.UtcNow; - - foreach (var item in items) - { - item.Id = Guid.NewGuid(); - item.CreatedAt = utcNow; - item.ModifiedAt = utcNow; - } - - context.OrderItems.AddRange(items); - return Task.FromResult>(items); - } - - public Task UpdateAsync(OrderItem orderItem, CancellationToken cancellationToken = default) - { - orderItem.ModifiedAt = DateTime.UtcNow; - context.OrderItems.Update(orderItem); - return Task.FromResult(orderItem); - } - - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) - { - var orderItem = await GetByIdAsync(id, cancellationToken); - if (orderItem != null) - { - context.OrderItems.Remove(orderItem); - } - } - - public async Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - var orderItems = await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .ToListAsync(cancellationToken); - - if (orderItems.Any()) - { - context.OrderItems.RemoveRange(orderItems); - } - } - - public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .AnyAsync(oi => oi.Id == id, cancellationToken); - } - - public async Task GetTotalValueByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.OrderId == orderId) - .SumAsync(oi => oi.TotalPrice, cancellationToken); - } - - public async Task GetQuantityByProductIdAsync(Guid productId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.ProductId == productId) - .SumAsync(oi => oi.Quantity, cancellationToken); - } - - public async Task GetQuantityByVariantIdAsync(Guid productVariantId, CancellationToken cancellationToken = default) - { - return await context.OrderItems - .Where(oi => oi.ProductVariantId == productVariantId) - .SumAsync(oi => oi.Quantity, cancellationToken); - } - - public async Task> GetProductSalesCountAsync( - DateTime? startDate = null, - DateTime? endDate = null, - CancellationToken cancellationToken = default) - { - var query = context.OrderItems - .Include(oi => oi.Order) - .AsQueryable(); - - if (startDate.HasValue) - { - query = query.Where(oi => oi.Order.OrderDate >= startDate.Value); - } - - if (endDate.HasValue) - { - query = query.Where(oi => oi.Order.OrderDate <= endDate.Value); - } - - return await query - .GroupBy(oi => oi.ProductId) - .Select(g => new { ProductId = g.Key, TotalQuantity = g.Sum(oi => oi.Quantity) }) - .ToDictionaryAsync(x => x.ProductId, x => x.TotalQuantity, cancellationToken); - } -} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs index 39a7655..1a11f67 100644 --- a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs @@ -1,5 +1,4 @@ using Imprink.Domain.Entities; -using Imprink.Domain.Models; using Imprink.Domain.Repositories; using Imprink.Infrastructure.Database; using Microsoft.EntityFrameworkCore; @@ -14,159 +13,79 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } - public async Task GetByIdWithItemsAsync(Guid id, CancellationToken cancellationToken = default) + public async Task GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.Product) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.ProductVariant) - .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); - } - - public async Task GetByIdWithAddressAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.OrderAddress) - .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); - } - - public async Task GetByIdFullAsync(Guid id, CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.User) .Include(o => o.OrderStatus) .Include(o => o.ShippingStatus) .Include(o => o.OrderAddress) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.Product) - .ThenInclude(p => p.Category) - .Include(o => o.OrderItems) - .ThenInclude(oi => oi.ProductVariant) + .Include(o => o.Product) + .Include(o => o.ProductVariant) + .Include(o => o.User) .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } public async Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) .FirstOrDefaultAsync(o => o.OrderNumber == orderNumber, cancellationToken); } - public async Task> GetPagedAsync( - OrderFilterParameters filterParameters, - CancellationToken cancellationToken = default) - { - var query = context.Orders - .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .AsQueryable(); - - if (!string.IsNullOrEmpty(filterParameters.UserId)) - { - query = query.Where(o => o.UserId == filterParameters.UserId); - } - - if (!string.IsNullOrEmpty(filterParameters.OrderNumber)) - { - query = query.Where(o => o.OrderNumber.Contains(filterParameters.OrderNumber)); - } - - if (filterParameters.OrderStatusId.HasValue) - { - query = query.Where(o => o.OrderStatusId == filterParameters.OrderStatusId.Value); - } - - if (filterParameters.ShippingStatusId.HasValue) - { - query = query.Where(o => o.ShippingStatusId == filterParameters.ShippingStatusId.Value); - } - - if (filterParameters.StartDate.HasValue) - { - query = query.Where(o => o.OrderDate >= filterParameters.StartDate.Value); - } - - if (filterParameters.EndDate.HasValue) - { - query = query.Where(o => o.OrderDate <= filterParameters.EndDate.Value); - } - - if (filterParameters.MinTotalPrice.HasValue) - { - query = query.Where(o => o.TotalPrice >= filterParameters.MinTotalPrice.Value); - } - - if (filterParameters.MaxTotalPrice.HasValue) - { - query = query.Where(o => o.TotalPrice <= filterParameters.MaxTotalPrice.Value); - } - - query = filterParameters.SortBy.ToLower() switch - { - "orderdate" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.OrderDate) - : query.OrderBy(o => o.OrderDate), - "totalprice" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.TotalPrice) - : query.OrderBy(o => o.TotalPrice), - "ordernumber" => filterParameters.SortDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase) - ? query.OrderByDescending(o => o.OrderNumber) - : query.OrderBy(o => o.OrderNumber), - _ => query.OrderByDescending(o => o.OrderDate) - }; - - var totalCount = await query.CountAsync(cancellationToken); - - var items = await query - .Skip((filterParameters.PageNumber - 1) * filterParameters.PageSize) - .Take(filterParameters.PageSize) - .ToListAsync(cancellationToken); - - return new PagedResult - { - Items = items, - TotalCount = totalCount, - PageNumber = filterParameters.PageNumber, - PageSize = filterParameters.PageSize - }; - } - public async Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } - public async Task> GetByUserIdPagedAsync( - string userId, - int pageNumber, - int pageSize, - CancellationToken cancellationToken = default) + public async Task> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default) { return await context.Orders .Include(o => o.OrderStatus) .Include(o => o.ShippingStatus) + .Include(o => o.OrderAddress) + .Include(o => o.Product) + .Include(o => o.ProductVariant) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OrderDate) - .Skip((pageNumber - 1) * pageSize) - .Take(pageSize) .ToListAsync(cancellationToken); } - public async Task> GetByOrderStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) + public async Task> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default) { return await context.Orders + .Where(o => o.MerchantId == merchantId) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByMerchantIdWithDetailsAsync(string merchantId, CancellationToken cancellationToken = default) + { + return await context.Orders + .Include(o => o.OrderStatus) + .Include(o => o.ShippingStatus) + .Include(o => o.OrderAddress) + .Include(o => o.Product) + .Include(o => o.ProductVariant) .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .Where(o => o.OrderStatusId == orderStatusId) + .Where(o => o.MerchantId == merchantId) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default) + { + return await context.Orders + .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate) + .OrderByDescending(o => o.OrderDate) + .ToListAsync(cancellationToken); + } + + public async Task> GetByStatusAsync(int statusId, CancellationToken cancellationToken = default) + { + return await context.Orders + .Where(o => o.OrderStatusId == statusId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } @@ -174,52 +93,39 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository public async Task> GetByShippingStatusAsync(int shippingStatusId, CancellationToken cancellationToken = default) { return await context.Orders - .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) .Where(o => o.ShippingStatusId == shippingStatusId) .OrderByDescending(o => o.OrderDate) .ToListAsync(cancellationToken); } - public async Task> GetByDateRangeAsync( - DateTime startDate, - DateTime endDate, - CancellationToken cancellationToken = default) - { - return await context.Orders - .Include(o => o.User) - .Include(o => o.OrderStatus) - .Include(o => o.ShippingStatus) - .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate) - .OrderByDescending(o => o.OrderDate) - .ToListAsync(cancellationToken); - } - public Task AddAsync(Order order, CancellationToken cancellationToken = default) { - order.Id = Guid.NewGuid(); - order.CreatedAt = DateTime.UtcNow; - order.ModifiedAt = DateTime.UtcNow; - context.Orders.Add(order); return Task.FromResult(order); } - public Task UpdateAsync(Order order, CancellationToken cancellationToken = default) + public async Task UpdateAsync(Order order, CancellationToken cancellationToken = default) { - order.ModifiedAt = DateTime.UtcNow; - context.Orders.Update(order); - return Task.FromResult(order); + var existingOrder = await context.Orders + .FirstOrDefaultAsync(o => o.Id == order.Id, cancellationToken); + + if (existingOrder == null) + throw new InvalidOperationException($"Order with ID {order.Id} not found"); + + context.Entry(existingOrder).CurrentValues.SetValues(order); + return existingOrder; } - public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) { - var order = await GetByIdAsync(id, cancellationToken); - if (order != null) - { - context.Orders.Remove(order); - } + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); + + if (order == null) + return false; + + context.Orders.Remove(order); + return true; } public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) @@ -228,38 +134,78 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .AnyAsync(o => o.Id == id, cancellationToken); } - public async Task OrderNumberExistsAsync(string orderNumber, Guid? excludeId = null, CancellationToken cancellationToken = default) + public async Task IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default) { - var query = context.Orders.Where(o => o.OrderNumber == orderNumber); - - if (excludeId.HasValue) + return !await context.Orders + .AnyAsync(o => o.OrderNumber == orderNumber, cancellationToken); + } + + public async Task IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default) + { + return !await context.Orders + .AnyAsync(o => o.OrderNumber == orderNumber && o.Id != excludeOrderId, cancellationToken); + } + + public async Task GenerateOrderNumberAsync(CancellationToken cancellationToken = default) + { + string orderNumber; + bool isUnique; + + do { - query = query.Where(o => o.Id != excludeId.Value); + // Generate order number format: ORD-YYYYMMDD-XXXXXX (where X is random) + var datePart = DateTime.UtcNow.ToString("yyyyMMdd"); + var randomPart = Random.Shared.Next(100000, 999999).ToString(); + orderNumber = $"ORD-{datePart}-{randomPart}"; + + isUnique = await IsOrderNumberUniqueAsync(orderNumber, cancellationToken); } + while (!isUnique); - return await query.AnyAsync(cancellationToken); + return orderNumber; } - public async Task GetTotalRevenueAsync(CancellationToken cancellationToken = default) + public async Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default) { - return await context.Orders - .Where(o => o.OrderStatusId != 5) // Assuming 5 is cancelled status - .SumAsync(o => o.TotalPrice, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.OrderStatusId = statusId; + } } - public async Task GetTotalRevenueByDateRangeAsync( - DateTime startDate, - DateTime endDate, - CancellationToken cancellationToken = default) + public async Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default) { - return await context.Orders - .Where(o => o.OrderDate >= startDate && o.OrderDate <= endDate && o.OrderStatusId != 5) - .SumAsync(o => o.TotalPrice, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.ShippingStatusId = shippingStatusId; + } } - public async Task GetOrderCountByStatusAsync(int orderStatusId, CancellationToken cancellationToken = default) + public async Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default) { - return await context.Orders - .CountAsync(o => o.OrderStatusId == orderStatusId, cancellationToken); + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.MerchantId = merchantId; + } + } + + public async Task UnassignMerchantAsync(Guid orderId, CancellationToken cancellationToken = default) + { + var order = await context.Orders + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order != null) + { + order.MerchantId = null; + } } } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/UnitOfWork.cs b/src/Imprink.Infrastructure/UnitOfWork.cs index 22011f2..29ae434 100644 --- a/src/Imprink.Infrastructure/UnitOfWork.cs +++ b/src/Imprink.Infrastructure/UnitOfWork.cs @@ -13,7 +13,7 @@ public class UnitOfWork( IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, IOrderRepository orderRepository, - IOrderItemRepository orderItemRepository) : IUnitOfWork + IAddressRepository addressRepository) : IUnitOfWork { public IProductRepository ProductRepository => productRepository; public IProductVariantRepository ProductVariantRepository => productVariantRepository; @@ -22,7 +22,7 @@ public class UnitOfWork( public IUserRoleRepository UserRoleRepository => userRoleRepository; public IRoleRepository RoleRepository => roleRepository; public IOrderRepository OrderRepository => orderRepository; - public IOrderItemRepository OrderItemRepository => orderItemRepository; + public IAddressRepository AddressRepository => addressRepository; public async Task SaveAsync(CancellationToken cancellationToken = default) { diff --git a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs index 86ee554..48e12fb 100644 --- a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs +++ b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs @@ -20,7 +20,7 @@ public static class StartupApplicationExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs index a1e30c0..67430c4 100644 --- a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs +++ b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs @@ -35,7 +35,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); -- 2.49.1 From c21c01c4322eb3db4bcf90d4e24873b4e7bf873f Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:34:39 +0300 Subject: [PATCH 2/8] Add Order/Address/OrderAddress repos, and handlers --- .../Addresses/CreateAddressHandler.cs | 52 +++++++++++++ .../Addresses/GetAddressByIdHandler.cs | 31 ++++++++ .../Addresses/GetAddressesByUserIdHandler.cs | 36 +++++++++ .../Commands/Orders/CreateOrderHandler.cs | 77 +++++++++++++++++++ .../Commands/Orders/GetOrderByIdHandler.cs | 31 ++++++++ .../Orders/GetOrdersByMerchantIdHandler.cs | 31 ++++++++ .../Orders/GetOrdersByUserIdHandler.cs | 31 ++++++++ src/Imprink.Application/Dtos/AddressDto.cs | 26 +++++++ .../Dtos/OrderAddressDto.cs | 24 ++++++ src/Imprink.Application/Dtos/OrderDto.cs | 30 ++++++++ .../Dtos/OrderStatusDto.cs | 8 ++ .../Dtos/ShippingStatusDto.cs | 8 ++ src/Imprink.Application/IUnitOfWork.cs | 1 + .../Imprink.Application.csproj | 4 - .../Repositories/IOrderAddressRepository.cs | 16 ++++ .../Database/ApplicationDbContext.cs | 2 +- .../Repositories/OrderAddressRepository.cs | 77 +++++++++++++++++++ src/Imprink.Infrastructure/UnitOfWork.cs | 4 +- .../StartupApplicationExtensions.cs | 1 + 19 files changed, 484 insertions(+), 6 deletions(-) create mode 100644 src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs create mode 100644 src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs create mode 100644 src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs create mode 100644 src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs create mode 100644 src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs create mode 100644 src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs create mode 100644 src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs create mode 100644 src/Imprink.Application/Dtos/AddressDto.cs create mode 100644 src/Imprink.Application/Dtos/OrderAddressDto.cs create mode 100644 src/Imprink.Application/Dtos/OrderDto.cs create mode 100644 src/Imprink.Application/Dtos/OrderStatusDto.cs create mode 100644 src/Imprink.Application/Dtos/ShippingStatusDto.cs create mode 100644 src/Imprink.Domain/Repositories/IOrderAddressRepository.cs create mode 100644 src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs new file mode 100644 index 0000000..5d8da24 --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class CreateAddressCommand : IRequest +{ + public string UserId { get; set; } = null!; + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } = true; +} + +public class CreateAddressHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +{ + public async Task Handle(CreateAddressCommand request, CancellationToken cancellationToken) + { + return await uw.TransactAsync(async () => + { + var address = mapper.Map
(request); + + if (address.IsDefault) + { + var currentDefault = await uw.AddressRepository.GetDefaultByUserIdAsync(address.UserId, cancellationToken); + if (currentDefault != null) + { + currentDefault.IsDefault = false; + await uw.AddressRepository.UpdateAsync(currentDefault, cancellationToken); + } + } + + var createdAddress = await uw.AddressRepository.AddAsync(address, cancellationToken); + return mapper.Map(createdAddress); + }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs b/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs new file mode 100644 index 0000000..3d14552 --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class GetAddressByIdQuery : IRequest +{ + public Guid Id { get; set; } + public string? UserId { get; set; } +} + +public class GetAddressByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +{ + public async Task Handle(GetAddressByIdQuery request, CancellationToken cancellationToken) + { + Address? address; + + if (!string.IsNullOrEmpty(request.UserId)) + { + address = await uw.AddressRepository.GetByIdAndUserIdAsync(request.Id, request.UserId, cancellationToken); + } + else + { + address = await uw.AddressRepository.GetByIdAsync(request.Id, cancellationToken); + } + + return address != null ? mapper.Map(address) : null; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs new file mode 100644 index 0000000..9d151cc --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class GetAddressesByUserIdQuery : IRequest> +{ + public string UserId { get; set; } = null!; + public bool ActiveOnly { get; set; } = false; + public string? AddressType { get; set; } +} + +public class GetAddressesByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +{ + public async Task> Handle(GetAddressesByUserIdQuery request, CancellationToken cancellationToken) + { + IEnumerable
addresses; + + if (!string.IsNullOrEmpty(request.AddressType)) + { + addresses = await uw.AddressRepository.GetByUserIdAndTypeAsync(request.UserId, request.AddressType, cancellationToken); + } + else if (request.ActiveOnly) + { + addresses = await uw.AddressRepository.GetActiveByUserIdAsync(request.UserId, cancellationToken); + } + else + { + addresses = await uw.AddressRepository.GetByUserIdAsync(request.UserId, cancellationToken); + } + + return mapper.Map>(addresses); + } +} diff --git a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs new file mode 100644 index 0000000..ac9a59c --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs @@ -0,0 +1,77 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class CreateOrderCommand : IRequest +{ + public string UserId { get; set; } = null!; + public decimal Amount { get; set; } + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid? ProductVariantId { get; set; } + public string? Notes { get; set; } + public string? MerchantId { get; set; } + public string? ComposingImageUrl { get; set; } + public string[] OriginalImageUrls { get; set; } = []; + public string CustomizationImageUrl { get; set; } = null!; + public string CustomizationDescription { get; set; } = null!; + + public Guid AddressId { get; set; } +} + +public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +{ + public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) + { + return await uw.TransactAsync(async () => + { + var sourceAddress = await uw.AddressRepository.GetByIdAndUserIdAsync(request.AddressId, request.UserId, cancellationToken); + if (sourceAddress == null) + { + throw new ArgumentException($"Address with ID {request.AddressId} not found for user {request.UserId}"); + } + + var order = mapper.Map(request); + order.OrderDate = DateTime.UtcNow; + order.OrderStatusId = 1; + order.ShippingStatusId = 1; + + var createdOrder = await uw.OrderRepository.AddAsync(order, cancellationToken); + + var orderAddress = new OrderAddress + { + OrderId = createdOrder.Id, + AddressType = sourceAddress.AddressType, + FirstName = sourceAddress.FirstName, + LastName = sourceAddress.LastName, + Company = sourceAddress.Company, + AddressLine1 = sourceAddress.AddressLine1, + AddressLine2 = sourceAddress.AddressLine2, + ApartmentNumber = sourceAddress.ApartmentNumber, + BuildingNumber = sourceAddress.BuildingNumber, + Floor = sourceAddress.Floor, + City = sourceAddress.City, + State = sourceAddress.State, + PostalCode = sourceAddress.PostalCode, + Country = sourceAddress.Country, + PhoneNumber = sourceAddress.PhoneNumber, + Instructions = sourceAddress.Instructions, + Order = createdOrder + }; + + await uw.OrderAddressRepository.AddAsync(orderAddress, cancellationToken); + + createdOrder.Product = (await uw.ProductRepository.GetByIdAsync(createdOrder.ProductId, cancellationToken))!; + + if (createdOrder.ProductVariantId.HasValue) + { + createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); + } + + return mapper.Map(createdOrder); + }, cancellationToken); + } +} diff --git a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs new file mode 100644 index 0000000..be46ab9 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrderByIdQuery : IRequest +{ + public Guid Id { get; set; } + public bool IncludeDetails { get; set; } = false; +} + +public class GetOrderByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +{ + public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) + { + Order? order; + + if (request.IncludeDetails) + { + order = await uw.OrderRepository.GetByIdWithDetailsAsync(request.Id, cancellationToken); + } + else + { + order = await uw.OrderRepository.GetByIdAsync(request.Id, cancellationToken); + } + + return order != null ? mapper.Map(order) : null; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs new file mode 100644 index 0000000..b34ea92 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrdersByMerchantIdQuery : IRequest> +{ + public string MerchantId { get; set; } = null!; + public bool IncludeDetails { get; set; } = false; +} + +public class GetOrdersByMerchantIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +{ + public async Task> Handle(GetOrdersByMerchantIdQuery request, CancellationToken cancellationToken) + { + IEnumerable orders; + + if (request.IncludeDetails) + { + orders = await uw.OrderRepository.GetByMerchantIdWithDetailsAsync(request.MerchantId, cancellationToken); + } + else + { + orders = await uw.OrderRepository.GetByMerchantIdAsync(request.MerchantId, cancellationToken); + } + + return mapper.Map>(orders); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs new file mode 100644 index 0000000..6f778a3 --- /dev/null +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Orders; + +public class GetOrdersByUserIdQuery : IRequest> +{ + public string UserId { get; set; } = null!; + public bool IncludeDetails { get; set; } = false; +} + +public class GetOrdersByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +{ + public async Task> Handle(GetOrdersByUserIdQuery request, CancellationToken cancellationToken) + { + IEnumerable orders; + + if (request.IncludeDetails) + { + orders = await uw.OrderRepository.GetByUserIdWithDetailsAsync(request.UserId, cancellationToken); + } + else + { + orders = await uw.OrderRepository.GetByUserIdAsync(request.UserId, cancellationToken); + } + + return mapper.Map>(orders); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/AddressDto.cs b/src/Imprink.Application/Dtos/AddressDto.cs new file mode 100644 index 0000000..6887b18 --- /dev/null +++ b/src/Imprink.Application/Dtos/AddressDto.cs @@ -0,0 +1,26 @@ +namespace Imprink.Application.Dtos; + +public class AddressDto +{ + public Guid Id { get; set; } + public string UserId { get; set; } = null!; + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public bool IsDefault { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderAddressDto.cs b/src/Imprink.Application/Dtos/OrderAddressDto.cs new file mode 100644 index 0000000..0fdfaad --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderAddressDto.cs @@ -0,0 +1,24 @@ +namespace Imprink.Application.Dtos; + +public class OrderAddressDto +{ + public Guid Id { get; set; } + public Guid OrderId { get; set; } + public string AddressType { get; set; } = null!; + public string? FirstName { get; set; } + public string? LastName { get; set; } + public string? Company { get; set; } + public string AddressLine1 { get; set; } = null!; + public string? AddressLine2 { get; set; } + public string? ApartmentNumber { get; set; } + public string? BuildingNumber { get; set; } + public string? Floor { get; set; } + public string City { get; set; } = null!; + public string State { get; set; } = null!; + public string PostalCode { get; set; } = null!; + public string Country { get; set; } = null!; + public string? PhoneNumber { get; set; } + public string? Instructions { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderDto.cs b/src/Imprink.Application/Dtos/OrderDto.cs new file mode 100644 index 0000000..9c1cb3f --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderDto.cs @@ -0,0 +1,30 @@ +namespace Imprink.Application.Dtos; + +public class OrderDto +{ + public Guid Id { get; set; } + public string UserId { get; set; } = null!; + public DateTime OrderDate { get; set; } + public decimal Amount { get; set; } + public int Quantity { get; set; } + public Guid ProductId { get; set; } + public Guid? ProductVariantId { get; set; } + public int OrderStatusId { get; set; } + public int ShippingStatusId { get; set; } + public string OrderNumber { get; set; } = null!; + public string? Notes { get; set; } + public string? MerchantId { get; set; } + public string? ComposingImageUrl { get; set; } + public string[] OriginalImageUrls { get; set; } = []; + public string CustomizationImageUrl { get; set; } = null!; + public string CustomizationDescription { get; set; } = null!; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + public OrderStatusDto? OrderStatus { get; set; } + public UserDto? User { get; set; } + public ShippingStatusDto? ShippingStatus { get; set; } + public OrderAddressDto? OrderAddress { get; set; } + public ProductDto? Product { get; set; } + public ProductVariantDto? ProductVariant { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderStatusDto.cs b/src/Imprink.Application/Dtos/OrderStatusDto.cs new file mode 100644 index 0000000..fb1622f --- /dev/null +++ b/src/Imprink.Application/Dtos/OrderStatusDto.cs @@ -0,0 +1,8 @@ +namespace Imprink.Application.Dtos; + +public class OrderStatusDto +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/ShippingStatusDto.cs b/src/Imprink.Application/Dtos/ShippingStatusDto.cs new file mode 100644 index 0000000..8ea670d --- /dev/null +++ b/src/Imprink.Application/Dtos/ShippingStatusDto.cs @@ -0,0 +1,8 @@ +namespace Imprink.Application.Dtos; + +public class ShippingStatusDto +{ + public int Id { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Imprink.Application/IUnitOfWork.cs b/src/Imprink.Application/IUnitOfWork.cs index 4655022..507fe10 100644 --- a/src/Imprink.Application/IUnitOfWork.cs +++ b/src/Imprink.Application/IUnitOfWork.cs @@ -12,6 +12,7 @@ public interface IUnitOfWork public IRoleRepository RoleRepository { get; } public IOrderRepository OrderRepository { get; } public IAddressRepository AddressRepository { get; } + public IOrderAddressRepository OrderAddressRepository { get; } Task SaveAsync(CancellationToken cancellationToken = default); Task BeginTransactionAsync(CancellationToken cancellationToken = default); diff --git a/src/Imprink.Application/Imprink.Application.csproj b/src/Imprink.Application/Imprink.Application.csproj index bde7f6a..17480c4 100644 --- a/src/Imprink.Application/Imprink.Application.csproj +++ b/src/Imprink.Application/Imprink.Application.csproj @@ -19,8 +19,4 @@ - - - - diff --git a/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs b/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs new file mode 100644 index 0000000..fb5f53d --- /dev/null +++ b/src/Imprink.Domain/Repositories/IOrderAddressRepository.cs @@ -0,0 +1,16 @@ +using Imprink.Domain.Entities; + +namespace Imprink.Domain.Repositories; + +public interface IOrderAddressRepository +{ + Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); + Task GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task> GetByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default); + Task AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default); + Task UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default); + Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); + Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); + Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); + Task ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs index 423bef6..b0f9746 100644 --- a/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs +++ b/src/Imprink.Infrastructure/Database/ApplicationDbContext.cs @@ -11,7 +11,6 @@ public class ApplicationDbContext(DbContextOptions options public DbSet Products { get; set; } public DbSet ProductVariants { get; set; } public DbSet Orders { get; set; } - public DbSet OrderAddresses { get; set; } public DbSet
Addresses { get; set; } public DbSet OrderStatuses { get; set; } public DbSet ShippingStatuses { get; set; } @@ -19,6 +18,7 @@ public class ApplicationDbContext(DbContextOptions options public DbSet UserRole { get; set; } public DbSet Roles { get; set; } public DbSet Categories { get; set; } + public DbSet OrderAddresses { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs new file mode 100644 index 0000000..33e44aa --- /dev/null +++ b/src/Imprink.Infrastructure/Repositories/OrderAddressRepository.cs @@ -0,0 +1,77 @@ +using Imprink.Domain.Entities; +using Imprink.Domain.Repositories; +using Imprink.Infrastructure.Database; +using Microsoft.EntityFrameworkCore; + +namespace Imprink.Infrastructure.Repositories; + +public class OrderAddressRepository(ApplicationDbContext context) : IOrderAddressRepository +{ + + public async Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .FirstOrDefaultAsync(oa => oa.Id == id, cancellationToken); + } + + public async Task GetByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .FirstOrDefaultAsync(oa => oa.OrderId == orderId, cancellationToken); + } + + public async Task> GetByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses + .Include(oa => oa.Order) + .Where(oa => orderIds.Contains(oa.OrderId)) + .ToListAsync(cancellationToken); + } + + public async Task AddAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default) + { + var entry = await context.OrderAddresses.AddAsync(orderAddress, cancellationToken); + await context.SaveChangesAsync(cancellationToken); + return entry.Entity; + } + + public async Task UpdateAsync(OrderAddress orderAddress, CancellationToken cancellationToken = default) + { + context.OrderAddresses.Update(orderAddress); + await context.SaveChangesAsync(cancellationToken); + return orderAddress; + } + + public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default) + { + var orderAddress = await context.OrderAddresses.FindAsync([id], cancellationToken); + if (orderAddress == null) + return false; + + context.OrderAddresses.Remove(orderAddress); + await context.SaveChangesAsync(cancellationToken); + return true; + } + + public async Task DeleteByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + var orderAddress = await context.OrderAddresses.FirstOrDefaultAsync(oa => oa.OrderId == orderId, cancellationToken); + if (orderAddress == null) + return false; + + context.OrderAddresses.Remove(orderAddress); + return true; + } + + public async Task ExistsAsync(Guid id, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses.AnyAsync(oa => oa.Id == id, cancellationToken); + } + + public async Task ExistsByOrderIdAsync(Guid orderId, CancellationToken cancellationToken = default) + { + return await context.OrderAddresses.AnyAsync(oa => oa.OrderId == orderId, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/UnitOfWork.cs b/src/Imprink.Infrastructure/UnitOfWork.cs index 29ae434..80e3472 100644 --- a/src/Imprink.Infrastructure/UnitOfWork.cs +++ b/src/Imprink.Infrastructure/UnitOfWork.cs @@ -13,7 +13,8 @@ public class UnitOfWork( IUserRoleRepository userRoleRepository, IRoleRepository roleRepository, IOrderRepository orderRepository, - IAddressRepository addressRepository) : IUnitOfWork + IAddressRepository addressRepository, + IOrderAddressRepository orderAddressRepository) : IUnitOfWork { public IProductRepository ProductRepository => productRepository; public IProductVariantRepository ProductVariantRepository => productVariantRepository; @@ -23,6 +24,7 @@ public class UnitOfWork( public IRoleRepository RoleRepository => roleRepository; public IOrderRepository OrderRepository => orderRepository; public IAddressRepository AddressRepository => addressRepository; + public IOrderAddressRepository OrderAddressRepository => orderAddressRepository; public async Task SaveAsync(CancellationToken cancellationToken = default) { diff --git a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs index 48e12fb..e10b03b 100644 --- a/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs +++ b/src/Imprink.WebApi/Extensions/StartupApplicationExtensions.cs @@ -20,6 +20,7 @@ public static class StartupApplicationExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); -- 2.49.1 From 266aa529fa9b24a9d249b6b1afcb80b364396d9f Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 00:16:26 +0300 Subject: [PATCH 3/8] Fixed erratic order behavior --- .../Addresses/CreateAddressHandler.cs | 16 +- .../Commands/Orders/CreateOrderHandler.cs | 22 +- src/Imprink.Application/Dtos/AddressDto.cs | 2 - src/Imprink.Application/Dtos/OrderDto.cs | 8 +- .../Mappings/AddressMappingProfile.cs | 23 + .../Mappings/OrderAddressMappingProfile.cs | 38 + .../Mappings/OrderMappingProfile.cs | 36 + .../Mappings/OrderStatusMappingProfile.cs | 15 + .../Mappings/ShippingStatusMappingProfile.cs | 15 + src/Imprink.Domain/Entities/Order.cs | 21 +- src/Imprink.Domain/Entities/OrderAddress.cs | 4 +- .../Repositories/IAddressRepository.cs | 4 +- .../Repositories/IOrderRepository.cs | 4 - .../Configuration/AddressConfiguration.cs | 3 +- .../EntityBaseConfiguration.cs | 10 +- .../Configuration/OrderConfiguration.cs | 19 +- .../Configuration/ProductConfiguration.cs | 12 - .../ProductVariantConfiguration.cs | 12 - .../Configuration/UserConfiguration.cs | 8 +- ...> 20250625211612_InitialSetup.Designer.cs} | 925 +++++++++--------- ...etup.cs => 20250625211612_InitialSetup.cs} | 261 +++-- .../ApplicationDbContextModelSnapshot.cs | 923 ++++++++--------- .../Repositories/AddressRepository.cs | 6 +- .../Repositories/OrderRepository.cs | 37 - src/Imprink.Infrastructure/UnitOfWork.cs | 2 - .../Controllers/AddressesController.cs | 60 ++ .../Controllers/OrdersController.cs | 74 ++ 27 files changed, 1381 insertions(+), 1179 deletions(-) create mode 100644 src/Imprink.Application/Mappings/AddressMappingProfile.cs create mode 100644 src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs create mode 100644 src/Imprink.Application/Mappings/OrderMappingProfile.cs create mode 100644 src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs create mode 100644 src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs rename src/Imprink.Infrastructure/{ => Configuration}/EntityBaseConfiguration.cs (84%) rename src/Imprink.Infrastructure/Migrations/{20250617163555_InitialSetup.Designer.cs => 20250625211612_InitialSetup.Designer.cs} (81%) rename src/Imprink.Infrastructure/Migrations/{20250617163555_InitialSetup.cs => 20250625211612_InitialSetup.cs} (87%) create mode 100644 src/Imprink.WebApi/Controllers/AddressesController.cs create mode 100644 src/Imprink.WebApi/Controllers/OrdersController.cs diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs index 5d8da24..83de43c 100644 --- a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs @@ -1,13 +1,14 @@ using AutoMapper; using Imprink.Application.Dtos; +using Imprink.Application.Services; using Imprink.Domain.Entities; using MediatR; +using Microsoft.Extensions.Logging; namespace Imprink.Application.Commands.Addresses; public class CreateAddressCommand : IRequest { - public string UserId { get; set; } = null!; public string AddressType { get; set; } = null!; public string? FirstName { get; set; } public string? LastName { get; set; } @@ -27,7 +28,12 @@ public class CreateAddressCommand : IRequest public bool IsActive { get; set; } = true; } -public class CreateAddressHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class CreateAddressHandler( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService, + ILogger logger) + : IRequestHandler { public async Task Handle(CreateAddressCommand request, CancellationToken cancellationToken) { @@ -35,6 +41,10 @@ public class CreateAddressHandler(IUnitOfWork uw, IMapper mapper) : IRequestHand { var address = mapper.Map
(request); + address.UserId = userService.GetCurrentUserId()!; + address.CreatedAt = DateTime.UtcNow; + address.ModifiedAt = DateTime.UtcNow; + if (address.IsDefault) { var currentDefault = await uw.AddressRepository.GetDefaultByUserIdAsync(address.UserId, cancellationToken); @@ -46,6 +56,8 @@ public class CreateAddressHandler(IUnitOfWork uw, IMapper mapper) : IRequestHand } var createdAddress = await uw.AddressRepository.AddAsync(address, cancellationToken); + + await uw.SaveAsync(cancellationToken); return mapper.Map(createdAddress); }, cancellationToken); } diff --git a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs index ac9a59c..55b09f6 100644 --- a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs +++ b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs @@ -1,5 +1,7 @@ using AutoMapper; using Imprink.Application.Dtos; +using Imprink.Application.Exceptions; +using Imprink.Application.Services; using Imprink.Domain.Entities; using MediatR; @@ -7,7 +9,6 @@ namespace Imprink.Application.Commands.Orders; public class CreateOrderCommand : IRequest { - public string UserId { get; set; } = null!; public decimal Amount { get; set; } public int Quantity { get; set; } public Guid ProductId { get; set; } @@ -15,29 +16,30 @@ public class CreateOrderCommand : IRequest public string? Notes { get; set; } public string? MerchantId { get; set; } public string? ComposingImageUrl { get; set; } - public string[] OriginalImageUrls { get; set; } = []; - public string CustomizationImageUrl { get; set; } = null!; - public string CustomizationDescription { get; set; } = null!; + public string[]? OriginalImageUrls { get; set; } = []; + public string? CustomizationImageUrl { get; set; } = null!; + public string? CustomizationDescription { get; set; } = null!; public Guid AddressId { get; set; } } -public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler { public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) { return await uw.TransactAsync(async () => { - var sourceAddress = await uw.AddressRepository.GetByIdAndUserIdAsync(request.AddressId, request.UserId, cancellationToken); + var sourceAddress = await uw.AddressRepository.GetByIdAndUserIdAsync(request.AddressId, userService.GetCurrentUserId()!, cancellationToken); if (sourceAddress == null) { - throw new ArgumentException($"Address with ID {request.AddressId} not found for user {request.UserId}"); + throw new NotFoundException($"Address with ID {request.AddressId} not found for user {userService.GetCurrentUserId()!}"); } var order = mapper.Map(request); + order.UserId = userService.GetCurrentUserId()!; order.OrderDate = DateTime.UtcNow; - order.OrderStatusId = 1; - order.ShippingStatusId = 1; + order.OrderStatusId = 0; + order.ShippingStatusId = 0; var createdOrder = await uw.OrderRepository.AddAsync(order, cancellationToken); @@ -70,6 +72,8 @@ public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandle { createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); } + + await uw.SaveAsync(cancellationToken); return mapper.Map(createdOrder); }, cancellationToken); diff --git a/src/Imprink.Application/Dtos/AddressDto.cs b/src/Imprink.Application/Dtos/AddressDto.cs index 6887b18..29512f5 100644 --- a/src/Imprink.Application/Dtos/AddressDto.cs +++ b/src/Imprink.Application/Dtos/AddressDto.cs @@ -21,6 +21,4 @@ public class AddressDto public string? Instructions { get; set; } public bool IsDefault { get; set; } public bool IsActive { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } } \ No newline at end of file diff --git a/src/Imprink.Application/Dtos/OrderDto.cs b/src/Imprink.Application/Dtos/OrderDto.cs index 9c1cb3f..947aea7 100644 --- a/src/Imprink.Application/Dtos/OrderDto.cs +++ b/src/Imprink.Application/Dtos/OrderDto.cs @@ -11,13 +11,11 @@ public class OrderDto public Guid? ProductVariantId { get; set; } public int OrderStatusId { get; set; } public int ShippingStatusId { get; set; } - public string OrderNumber { get; set; } = null!; public string? Notes { get; set; } public string? MerchantId { get; set; } - public string? ComposingImageUrl { get; set; } - public string[] OriginalImageUrls { get; set; } = []; - public string CustomizationImageUrl { get; set; } = null!; - public string CustomizationDescription { get; set; } = null!; + public string? CustomizationImageUrl { get; set; } + public string[]? OriginalImageUrls { get; set; } = []; + public string? CustomizationDescription { get; set; } = null!; public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } diff --git a/src/Imprink.Application/Mappings/AddressMappingProfile.cs b/src/Imprink.Application/Mappings/AddressMappingProfile.cs new file mode 100644 index 0000000..13236f4 --- /dev/null +++ b/src/Imprink.Application/Mappings/AddressMappingProfile.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using Imprink.Application.Commands.Addresses; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class AddressMappingProfile : Profile +{ + public AddressMappingProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.User, opt => opt.Ignore()); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs b/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs new file mode 100644 index 0000000..6524393 --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderAddressMappingProfile.cs @@ -0,0 +1,38 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderAddressMappingProfile : Profile +{ + public OrderAddressMappingProfile() + { + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Order, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.OrderId, opt => opt.Ignore()) + .ForMember(dest => dest.Order, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.AddressType, opt => opt.MapFrom(src => src.AddressType)) + .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FirstName)) + .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.LastName)) + .ForMember(dest => dest.Company, opt => opt.MapFrom(src => src.Company)) + .ForMember(dest => dest.AddressLine1, opt => opt.MapFrom(src => src.AddressLine1)) + .ForMember(dest => dest.AddressLine2, opt => opt.MapFrom(src => src.AddressLine2)) + .ForMember(dest => dest.ApartmentNumber, opt => opt.MapFrom(src => src.ApartmentNumber)) + .ForMember(dest => dest.BuildingNumber, opt => opt.MapFrom(src => src.BuildingNumber)) + .ForMember(dest => dest.Floor, opt => opt.MapFrom(src => src.Floor)) + .ForMember(dest => dest.City, opt => opt.MapFrom(src => src.City)) + .ForMember(dest => dest.State, opt => opt.MapFrom(src => src.State)) + .ForMember(dest => dest.PostalCode, opt => opt.MapFrom(src => src.PostalCode)) + .ForMember(dest => dest.Country, opt => opt.MapFrom(src => src.Country)) + .ForMember(dest => dest.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber)) + .ForMember(dest => dest.Instructions, opt => opt.MapFrom(src => src.Instructions)); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderMappingProfile.cs b/src/Imprink.Application/Mappings/OrderMappingProfile.cs new file mode 100644 index 0000000..82a83ad --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderMappingProfile.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using Imprink.Application.Commands.Orders; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderMappingProfile : Profile +{ + public OrderMappingProfile() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.OrderDate, opt => opt.Ignore()) + .ForMember(dest => dest.OrderStatusId, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatusId, opt => opt.Ignore()) + .ForMember(dest => dest.CreatedAt, opt => opt.Ignore()) + .ForMember(dest => dest.ModifiedAt, opt => opt.Ignore()) + .ForMember(dest => dest.OrderStatus, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatus, opt => opt.Ignore()) + .ForMember(dest => dest.OrderAddress, opt => opt.Ignore()) + .ForMember(dest => dest.Product, opt => opt.Ignore()) + .ForMember(dest => dest.ProductVariant, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ForMember(dest => dest.OrderStatus, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ForMember(dest => dest.ShippingStatus, opt => opt.Ignore()) + .ForMember(dest => dest.OrderAddress, opt => opt.Ignore()) + .ForMember(dest => dest.Product, opt => opt.Ignore()) + .ForMember(dest => dest.ProductVariant, opt => opt.Ignore()); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs b/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs new file mode 100644 index 0000000..c61c7fe --- /dev/null +++ b/src/Imprink.Application/Mappings/OrderStatusMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class OrderStatusMappingProfile : Profile +{ + public OrderStatusMappingProfile() + { + CreateMap(); + + CreateMap(); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs b/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs new file mode 100644 index 0000000..16a60a5 --- /dev/null +++ b/src/Imprink.Application/Mappings/ShippingStatusMappingProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Domain.Entities; + +namespace Imprink.Application.Mappings; + +public class ShippingStatusMappingProfile : Profile +{ + public ShippingStatusMappingProfile() + { + CreateMap(); + + CreateMap(); + } +} \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/Order.cs b/src/Imprink.Domain/Entities/Order.cs index 144d8b3..e67ff02 100644 --- a/src/Imprink.Domain/Entities/Order.cs +++ b/src/Imprink.Domain/Entities/Order.cs @@ -2,7 +2,7 @@ namespace Imprink.Domain.Entities; public class Order : EntityBase { - public string UserId { get; set; } = null!; + public required string UserId { get; set; } public DateTime OrderDate { get; set; } public decimal Amount { get; set; } public int Quantity { get; set; } @@ -10,18 +10,17 @@ public class Order : EntityBase public Guid? ProductVariantId { get; set; } public int OrderStatusId { get; set; } public int ShippingStatusId { get; set; } - public string OrderNumber { get; set; } = null!; public string? Notes { get; set; } public string? MerchantId { get; set; } - public string? ComposingImageUrl { get; set; } + public string? CustomizationImageUrl { get; set; } public string[] OriginalImageUrls { get; set; } = []; - public string CustomizationImageUrl { get; set; } = null!; - public string CustomizationDescription { get; set; } = null!; + public string? CustomizationDescription { get; set; } - public OrderStatus OrderStatus { get; set; } = null!; - public User User { get; set; } = null!; - public ShippingStatus ShippingStatus { get; set; } = null!; - public OrderAddress OrderAddress { get; set; } = null!; - public Product Product { get; set; } = null!; - public ProductVariant? ProductVariant { get; set; } + public virtual OrderStatus OrderStatus { get; set; } = null!; + public virtual User User { get; set; } = null!; + public virtual User? Merchant { get; set; } + public virtual ShippingStatus ShippingStatus { get; set; } = null!; + public virtual OrderAddress OrderAddress { get; set; } = null!; + public virtual Product Product { get; set; } = null!; + public virtual ProductVariant? ProductVariant { get; set; } } \ No newline at end of file diff --git a/src/Imprink.Domain/Entities/OrderAddress.cs b/src/Imprink.Domain/Entities/OrderAddress.cs index 944fe37..49fb270 100644 --- a/src/Imprink.Domain/Entities/OrderAddress.cs +++ b/src/Imprink.Domain/Entities/OrderAddress.cs @@ -18,6 +18,6 @@ public class OrderAddress : EntityBase public required string Country { get; set; } public string? PhoneNumber { get; set; } public string? Instructions { get; set; } - - public virtual required Order Order { get; set; } + + public virtual Order Order { get; set; } = null!; } \ No newline at end of file diff --git a/src/Imprink.Domain/Repositories/IAddressRepository.cs b/src/Imprink.Domain/Repositories/IAddressRepository.cs index 911061d..61267ce 100644 --- a/src/Imprink.Domain/Repositories/IAddressRepository.cs +++ b/src/Imprink.Domain/Repositories/IAddressRepository.cs @@ -6,7 +6,7 @@ public interface IAddressRepository { Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); Task> GetActiveByUserIdAsync(string userId, CancellationToken cancellationToken = default); - Task GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default); + Task GetDefaultByUserIdAsync(string? userId, CancellationToken cancellationToken = default); Task> GetByUserIdAndTypeAsync(string userId, string addressType, CancellationToken cancellationToken = default); Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); Task GetByIdAndUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); @@ -14,7 +14,7 @@ public interface IAddressRepository Task
UpdateAsync(Address address, CancellationToken cancellationToken = default); Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task DeleteByUserIdAsync(Guid id, string userId, CancellationToken cancellationToken = default); - Task SetDefaultAddressAsync(string userId, Guid addressId, CancellationToken cancellationToken = default); + Task SetDefaultAddressAsync(string? userId, Guid addressId, CancellationToken cancellationToken = default); Task DeactivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); Task ActivateAddressAsync(Guid addressId, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); diff --git a/src/Imprink.Domain/Repositories/IOrderRepository.cs b/src/Imprink.Domain/Repositories/IOrderRepository.cs index d5a95fb..01e7276 100644 --- a/src/Imprink.Domain/Repositories/IOrderRepository.cs +++ b/src/Imprink.Domain/Repositories/IOrderRepository.cs @@ -7,7 +7,6 @@ public interface IOrderRepository { Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); Task GetByIdWithDetailsAsync(Guid id, CancellationToken cancellationToken = default); - Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default); Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default); Task> GetByUserIdWithDetailsAsync(string userId, CancellationToken cancellationToken = default); Task> GetByMerchantIdAsync(string merchantId, CancellationToken cancellationToken = default); @@ -19,9 +18,6 @@ public interface IOrderRepository Task UpdateAsync(Order order, CancellationToken cancellationToken = default); Task DeleteAsync(Guid id, CancellationToken cancellationToken = default); Task ExistsAsync(Guid id, CancellationToken cancellationToken = default); - Task IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default); - Task IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default); - Task GenerateOrderNumberAsync(CancellationToken cancellationToken = default); Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default); Task UpdateShippingStatusAsync(Guid orderId, int shippingStatusId, CancellationToken cancellationToken = default); Task AssignMerchantAsync(Guid orderId, string merchantId, CancellationToken cancellationToken = default); diff --git a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs index 6a1bcce..be115a3 100644 --- a/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/AddressConfiguration.cs @@ -75,7 +75,6 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.HasOne(a => a.User) .WithMany(u => u.Addresses) .HasForeignKey(a => a.UserId) - .HasPrincipalKey(u => u.Id) .OnDelete(DeleteBehavior.Cascade); builder.HasIndex(a => a.UserId) @@ -87,4 +86,4 @@ public class AddressConfiguration : EntityBaseConfiguration
builder.HasIndex(a => new { a.UserId, a.IsDefault }) .HasDatabaseName("IX_Address_User_Default"); } -} +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/EntityBaseConfiguration.cs b/src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs similarity index 84% rename from src/Imprink.Infrastructure/EntityBaseConfiguration.cs rename to src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs index 4e4f717..30f376e 100644 --- a/src/Imprink.Infrastructure/EntityBaseConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/EntityBaseConfiguration.cs @@ -2,7 +2,7 @@ using Imprink.Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Imprink.Infrastructure; +namespace Imprink.Infrastructure.Configuration; public class EntityBaseConfiguration : IEntityTypeConfiguration where T : EntityBase { @@ -12,20 +12,16 @@ public class EntityBaseConfiguration : IEntityTypeConfiguration where T : builder.Property(e => e.Id) .HasDefaultValueSql("NEWID()"); - - builder.Property(e => e.CreatedAt) - .IsRequired(); + + builder.Property(e => e.CreatedAt); builder.Property(e => e.ModifiedAt) - .IsRequired() .HasDefaultValueSql("GETUTCDATE()"); builder.Property(e => e.CreatedBy) - .IsRequired() .HasMaxLength(450); builder.Property(e => e.ModifiedBy) - .IsRequired() .HasMaxLength(450); builder.HasIndex(e => e.CreatedAt) diff --git a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs index 0aebad5..1683554 100644 --- a/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/OrderConfiguration.cs @@ -35,19 +35,12 @@ public class OrderConfiguration : EntityBaseConfiguration builder.Property(o => o.ShippingStatusId) .IsRequired(); - builder.Property(o => o.OrderNumber) - .IsRequired() - .HasMaxLength(50); - builder.Property(o => o.Notes) .HasMaxLength(1000); builder.Property(o => o.MerchantId) .HasMaxLength(450); - builder.Property(o => o.ComposingImageUrl) - .HasMaxLength(1000); - builder.Property(o => o.OriginalImageUrls) .HasConversion( v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null), @@ -55,11 +48,9 @@ public class OrderConfiguration : EntityBaseConfiguration .HasColumnType("nvarchar(max)"); builder.Property(o => o.CustomizationImageUrl) - .IsRequired() .HasMaxLength(1000); builder.Property(o => o.CustomizationDescription) - .IsRequired() .HasMaxLength(2000); builder.HasOne(o => o.OrderStatus) @@ -80,13 +71,11 @@ public class OrderConfiguration : EntityBaseConfiguration builder.HasOne(o => o.User) .WithMany(u => u.Orders) .HasForeignKey(o => o.UserId) - .HasPrincipalKey(u => u.Id) .OnDelete(DeleteBehavior.Restrict); - builder.HasOne() + builder.HasOne(o => o.Merchant) .WithMany(u => u.MerchantOrders) .HasForeignKey(o => o.MerchantId) - .HasPrincipalKey(u => u.Id) .OnDelete(DeleteBehavior.SetNull); builder.HasOne(o => o.Product) @@ -102,10 +91,6 @@ public class OrderConfiguration : EntityBaseConfiguration builder.HasIndex(o => o.UserId) .HasDatabaseName("IX_Order_UserId"); - builder.HasIndex(o => o.OrderNumber) - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - builder.HasIndex(o => o.OrderDate) .HasDatabaseName("IX_Order_OrderDate"); @@ -133,4 +118,4 @@ public class OrderConfiguration : EntityBaseConfiguration builder.HasIndex(o => new { o.ProductId, o.OrderDate }) .HasDatabaseName("IX_Order_Product_Date"); } -} +} \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs b/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs index c0ef17f..31db7d8 100644 --- a/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/ProductConfiguration.cs @@ -34,18 +34,6 @@ public class ProductConfiguration : EntityBaseConfiguration builder.Property(p => p.CategoryId) .IsRequired(false); - - builder.Property(c => c.CreatedAt) - .IsRequired(false); - - builder.Property(c => c.CreatedBy) - .IsRequired(false); - - builder.Property(c => c.ModifiedAt) - .IsRequired(false); - - builder.Property(c => c.ModifiedBy) - .IsRequired(false); builder.HasOne(p => p.Category) .WithMany(c => c.Products) diff --git a/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs b/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs index 2d323a1..89de1e3 100644 --- a/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/ProductVariantConfiguration.cs @@ -37,18 +37,6 @@ public class ProductVariantConfiguration : EntityBaseConfiguration pv.IsActive) .IsRequired() .HasDefaultValue(true); - - builder.Property(c => c.CreatedAt) - .IsRequired(false); - - builder.Property(c => c.CreatedBy) - .IsRequired(false); - - builder.Property(c => c.ModifiedAt) - .IsRequired(false); - - builder.Property(c => c.ModifiedBy) - .IsRequired(false); builder.HasOne(pv => pv.Product) .WithMany(p => p.ProductVariants) diff --git a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs index 3725f6c..42f4a54 100644 --- a/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs +++ b/src/Imprink.Infrastructure/Configuration/UserConfiguration.cs @@ -8,6 +8,8 @@ public class UserConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasKey(u => u.Id); + builder.Property(u => u.Id) .HasMaxLength(450) .ValueGeneratedNever(); @@ -47,12 +49,6 @@ public class UserConfiguration : IEntityTypeConfiguration builder.HasIndex(u => u.IsActive) .HasDatabaseName("IX_User_IsActive"); - builder.HasMany(u => u.Addresses) - .WithOne() - .HasForeignKey(a => a.UserId) - .HasPrincipalKey(u => u.Id) - .OnDelete(DeleteBehavior.Cascade); - builder.Ignore(u => u.DefaultAddress); builder.Ignore(u => u.Roles); } diff --git a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs similarity index 81% rename from src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs rename to src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs index 18e4c1b..65b0a44 100644 --- a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.Designer.cs +++ b/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Imprink.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20250617163555_InitialSetup")] + [Migration("20250625211612_InitialSetup")] partial class InitialSetup { /// @@ -25,128 +25,94 @@ namespace Imprink.Infrastructure.Migrations SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier") .HasDefaultValueSql("NEWID()"); - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("AddressLine1") .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderNumber") + b.Property("AddressType") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("OrderStatusId") - .HasColumnType("int"); + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderNumber") - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("City") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("Country") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("CreatedAt") + b.Property("CreatedAt") .HasColumnType("datetime2"); b.Property("CreatedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("ModifiedAt") + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") .ValueGeneratedOnAdd() .HasColumnType("datetime2") .HasDefaultValueSql("GETUTCDATE()"); b.Property("ModifiedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("PostalCode") .IsRequired() @@ -158,193 +124,35 @@ namespace Imprink.Infrastructure.Migrations .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("UserId") .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("CustomizationDescription") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - b.HasKey("Id"); b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderItem_CreatedAt"); + .HasDatabaseName("IX_Address_CreatedAt"); b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderItem_CreatedBy"); + .HasDatabaseName("IX_Address_CreatedBy"); b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderItem_ModifiedAt"); + .HasDatabaseName("IX_Address_ModifiedAt"); - b.HasIndex("OrderId") - .HasDatabaseName("IX_OrderItem_OrderId"); + b.HasIndex("UserId") + .HasDatabaseName("IX_Address_UserId"); - b.HasIndex("ProductId") - .HasDatabaseName("IX_OrderItem_ProductId"); + b.HasIndex("UserId", "AddressType") + .HasDatabaseName("IX_Address_User_Type"); - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_OrderItem_ProductVariantId"); + b.HasIndex("UserId", "IsDefault") + .HasDatabaseName("IX_Address_User_Default"); - b.HasIndex("OrderId", "ProductId") - .HasDatabaseName("IX_OrderItem_Order_Product"); - - b.ToTable("OrderItems"); + b.ToTable("Addresses"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -461,7 +269,273 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("CustomizationDescription") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CustomizationImageUrl") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MerchantId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderStatusId") + .HasColumnType("int"); + + b.Property("OriginalImageUrls") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductVariantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("ShippingStatusId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_Order_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_Order_CreatedBy"); + + b.HasIndex("MerchantId") + .HasDatabaseName("IX_Order_MerchantId"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_Order_ModifiedAt"); + + b.HasIndex("OrderDate") + .HasDatabaseName("IX_Order_OrderDate"); + + b.HasIndex("OrderStatusId") + .HasDatabaseName("IX_Order_OrderStatusId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_Order_ProductId"); + + b.HasIndex("ProductVariantId") + .HasDatabaseName("IX_Order_ProductVariantId"); + + b.HasIndex("ShippingStatusId") + .HasDatabaseName("IX_Order_ShippingStatusId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_Order_UserId"); + + b.HasIndex("MerchantId", "OrderDate") + .HasDatabaseName("IX_Order_Merchant_Date"); + + b.HasIndex("ProductId", "OrderDate") + .HasDatabaseName("IX_Order_Product_Date"); + + b.HasIndex("UserId", "OrderDate") + .HasDatabaseName("IX_Order_User_Date"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("AddressLine1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_OrderAddress_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_OrderAddress_CreatedBy"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_OrderAddress_ModifiedAt"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderAddress_OrderId"); + + b.ToTable("OrderAddresses"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_OrderStatus_Name"); + + b.ToTable("OrderStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Pending" + }, + new + { + Id = 1, + Name = "Processing" + }, + new + { + Id = 2, + Name = "Completed" + }, + new + { + Id = 3, + Name = "Cancelled" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -545,7 +619,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Products"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -631,100 +705,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -755,7 +736,48 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_ShippingStatus_Name"); + + b.ToTable("ShippingStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Prepping" + }, + new + { + Id = 1, + Name = "Packaging" + }, + new + { + Id = 2, + Name = "Shipped" + }, + new + { + Id = 3, + Name = "Delivered" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Property("Id") .HasMaxLength(450) @@ -767,7 +789,6 @@ namespace Imprink.Infrastructure.Migrations .HasColumnType("nvarchar(256)"); b.Property("EmailVerified") - .HasMaxLength(100) .HasColumnType("bit"); b.Property("FirstName") @@ -809,7 +830,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { b.Property("UserId") .HasMaxLength(450) @@ -829,73 +850,20 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("UserRole"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { - b.HasOne("Imprink.Domain.Entities.Orders.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Orders.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Users.User", "User") - .WithMany("Orders") + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Addresses") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderStatus"); - - b.Navigation("ShippingStatus"); - b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.Orders.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.ProductVariant", "ProductVariant") - .WithMany("OrderItems") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Order"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Product.Category", "ParentCategory") + b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") .WithMany("SubCategories") .HasForeignKey("ParentCategoryId") .OnDelete(DeleteBehavior.Restrict); @@ -903,9 +871,69 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("ParentCategory"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.HasOne("Imprink.Domain.Entities.Product.Category", "Category") + b.HasOne("Imprink.Domain.Entities.User", "Merchant") + .WithMany("MerchantOrders") + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") + .WithMany("Orders") + .HasForeignKey("OrderStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.Product", "Product") + .WithMany("Orders") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany("Orders") + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") + .WithMany("Orders") + .HasForeignKey("ShippingStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Merchant"); + + b.Navigation("OrderStatus"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + + b.Navigation("ShippingStatus"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.HasOne("Imprink.Domain.Entities.Order", "Order") + .WithOne("OrderAddress") + .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.HasOne("Imprink.Domain.Entities.Category", "Category") .WithMany("Products") .HasForeignKey("CategoryId") .OnDelete(DeleteBehavior.SetNull); @@ -913,9 +941,9 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Category"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") + b.HasOne("Imprink.Domain.Entities.Product", "Product") .WithMany("ProductVariants") .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade) @@ -924,24 +952,15 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Product"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { - b.HasOne("Imprink.Domain.Entities.Users.User", null) - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Users.Role", "Role") + b.HasOne("Imprink.Domain.Entities.Role", "Role") .WithMany("UserRoles") .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Imprink.Domain.Entities.Users.User", "User") + b.HasOne("Imprink.Domain.Entities.User", "User") .WithMany("UserRoles") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -952,52 +971,52 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - - b.Navigation("OrderItems"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Navigation("Products"); b.Navigation("SubCategories"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.Navigation("OrderItems"); + b.Navigation("OrderAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.Navigation("Orders"); b.Navigation("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.Navigation("OrderItems"); + b.Navigation("Orders"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Navigation("UserRoles"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Navigation("Addresses"); + b.Navigation("MerchantOrders"); + b.Navigation("Orders"); b.Navigation("UserRoles"); diff --git a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.cs similarity index 87% rename from src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs rename to src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.cs index 30c801b..9606a5c 100644 --- a/src/Imprink.Infrastructure/Migrations/20250617163555_InitialSetup.cs +++ b/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.cs @@ -84,7 +84,7 @@ namespace Imprink.Infrastructure.Migrations Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Nickname = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - EmailVerified = table.Column(type: "bit", maxLength: 100, nullable: false), + EmailVerified = table.Column(type: "bit", nullable: false), FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), @@ -130,17 +130,26 @@ namespace Imprink.Infrastructure.Migrations Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Street = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), IsDefault = table.Column(type: "bit", nullable: false, defaultValue: false), IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) }, constraints: table => { @@ -153,46 +162,6 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "Orders", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - OrderDate = table.Column(type: "datetime2", nullable: false), - TotalPrice = table.Column(type: "decimal(18,2)", nullable: false), - OrderStatusId = table.Column(type: "int", nullable: false), - ShippingStatusId = table.Column(type: "int", nullable: false), - OrderNumber = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - table.ForeignKey( - name: "FK_Orders_OrderStatuses_OrderStatusId", - column: x => x.OrderStatusId, - principalTable: "OrderStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_ShippingStatuses_ShippingStatusId", - column: x => x.ShippingStatusId, - principalTable: "ShippingStatuses", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Orders_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - migrationBuilder.CreateTable( name: "UserRole", columns: table => new @@ -246,21 +215,95 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Orders", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), + UserId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), + OrderDate = table.Column(type: "datetime2", nullable: false), + Amount = table.Column(type: "decimal(18,2)", nullable: false), + Quantity = table.Column(type: "int", nullable: false, defaultValue: 1), + ProductId = table.Column(type: "uniqueidentifier", nullable: false), + ProductVariantId = table.Column(type: "uniqueidentifier", nullable: true), + OrderStatusId = table.Column(type: "int", nullable: false), + ShippingStatusId = table.Column(type: "int", nullable: false), + Notes = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + MerchantId = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + CustomizationImageUrl = table.Column(type: "nvarchar(1000)", maxLength: 1000, nullable: true), + OriginalImageUrls = table.Column(type: "nvarchar(max)", nullable: false), + CustomizationDescription = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + name: "FK_Orders_OrderStatuses_OrderStatusId", + column: x => x.OrderStatusId, + principalTable: "OrderStatuses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_ProductVariants_ProductVariantId", + column: x => x.ProductVariantId, + principalTable: "ProductVariants", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_ShippingStatuses_ShippingStatusId", + column: x => x.ShippingStatusId, + principalTable: "ShippingStatuses", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Orders_Users_MerchantId", + column: x => x.MerchantId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Orders_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "OrderAddresses", columns: table => new { Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), OrderId = table.Column(type: "uniqueidentifier", nullable: false), - Street = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressType = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + FirstName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + LastName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + Company = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + AddressLine1 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + AddressLine2 = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + ApartmentNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + BuildingNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Floor = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), City = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), State = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), PostalCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), Country = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) + PhoneNumber = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: true), + Instructions = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + CreatedAt = table.Column(type: "datetime2", nullable: true), + ModifiedAt = table.Column(type: "datetime2", nullable: true, defaultValueSql: "GETUTCDATE()"), + CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true), + ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: true) }, constraints: table => { @@ -273,47 +316,6 @@ namespace Imprink.Infrastructure.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "OrderItems", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"), - OrderId = table.Column(type: "uniqueidentifier", nullable: false), - ProductId = table.Column(type: "uniqueidentifier", nullable: false), - ProductVariantId = table.Column(type: "uniqueidentifier", nullable: true), - Quantity = table.Column(type: "int", nullable: false, defaultValue: 1), - UnitPrice = table.Column(type: "decimal(18,2)", nullable: false), - TotalPrice = table.Column(type: "decimal(18,2)", nullable: false), - CustomizationImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), - CustomizationDescription = table.Column(type: "nvarchar(2000)", maxLength: 2000, nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - ModifiedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), - CreatedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false), - ModifiedBy = table.Column(type: "nvarchar(450)", maxLength: 450, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => x.Id); - table.ForeignKey( - name: "FK_OrderItems_Orders_OrderId", - column: x => x.OrderId, - principalTable: "Orders", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_OrderItems_ProductVariants_ProductVariantId", - column: x => x.ProductVariantId, - principalTable: "ProductVariants", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_OrderItems_Products_ProductId", - column: x => x.ProductId, - principalTable: "Products", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - migrationBuilder.InsertData( table: "Categories", columns: new[] { "Id", "CreatedAt", "CreatedBy", "Description", "ImageUrl", "IsActive", "ModifiedAt", "ModifiedBy", "Name", "ParentCategoryId", "SortOrder" }, @@ -446,41 +448,6 @@ namespace Imprink.Infrastructure.Migrations column: "OrderId", unique: true); - migrationBuilder.CreateIndex( - name: "IX_OrderItem_CreatedAt", - table: "OrderItems", - column: "CreatedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_CreatedBy", - table: "OrderItems", - column: "CreatedBy"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ModifiedAt", - table: "OrderItems", - column: "ModifiedAt"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_Order_Product", - table: "OrderItems", - columns: new[] { "OrderId", "ProductId" }); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_OrderId", - table: "OrderItems", - column: "OrderId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ProductId", - table: "OrderItems", - column: "ProductId"); - - migrationBuilder.CreateIndex( - name: "IX_OrderItem_ProductVariantId", - table: "OrderItems", - column: "ProductVariantId"); - migrationBuilder.CreateIndex( name: "IX_Order_CreatedAt", table: "Orders", @@ -491,6 +458,16 @@ namespace Imprink.Infrastructure.Migrations table: "Orders", column: "CreatedBy"); + migrationBuilder.CreateIndex( + name: "IX_Order_Merchant_Date", + table: "Orders", + columns: new[] { "MerchantId", "OrderDate" }); + + migrationBuilder.CreateIndex( + name: "IX_Order_MerchantId", + table: "Orders", + column: "MerchantId"); + migrationBuilder.CreateIndex( name: "IX_Order_ModifiedAt", table: "Orders", @@ -501,17 +478,26 @@ namespace Imprink.Infrastructure.Migrations table: "Orders", column: "OrderDate"); - migrationBuilder.CreateIndex( - name: "IX_Order_OrderNumber", - table: "Orders", - column: "OrderNumber", - unique: true); - migrationBuilder.CreateIndex( name: "IX_Order_OrderStatusId", table: "Orders", column: "OrderStatusId"); + migrationBuilder.CreateIndex( + name: "IX_Order_Product_Date", + table: "Orders", + columns: new[] { "ProductId", "OrderDate" }); + + migrationBuilder.CreateIndex( + name: "IX_Order_ProductId", + table: "Orders", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_Order_ProductVariantId", + table: "Orders", + column: "ProductVariantId"); + migrationBuilder.CreateIndex( name: "IX_Order_ShippingStatusId", table: "Orders", @@ -659,24 +645,21 @@ namespace Imprink.Infrastructure.Migrations migrationBuilder.DropTable( name: "OrderAddresses"); - migrationBuilder.DropTable( - name: "OrderItems"); - migrationBuilder.DropTable( name: "UserRole"); migrationBuilder.DropTable( name: "Orders"); - migrationBuilder.DropTable( - name: "ProductVariants"); - migrationBuilder.DropTable( name: "Roles"); migrationBuilder.DropTable( name: "OrderStatuses"); + migrationBuilder.DropTable( + name: "ProductVariants"); + migrationBuilder.DropTable( name: "ShippingStatuses"); diff --git a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs index 1195e8b..9d264b9 100644 --- a/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Imprink.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -22,128 +22,94 @@ namespace Imprink.Infrastructure.Migrations SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier") .HasDefaultValueSql("NEWID()"); - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("AddressLine1") .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("Notes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("OrderDate") - .HasColumnType("datetime2"); - - b.Property("OrderNumber") + b.Property("AddressType") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); - b.Property("OrderStatusId") - .HasColumnType("int"); + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); - b.Property("ShippingStatusId") - .HasColumnType("int"); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Order_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Order_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Order_ModifiedAt"); - - b.HasIndex("OrderDate") - .HasDatabaseName("IX_Order_OrderDate"); - - b.HasIndex("OrderNumber") - .IsUnique() - .HasDatabaseName("IX_Order_OrderNumber"); - - b.HasIndex("OrderStatusId") - .HasDatabaseName("IX_Order_OrderStatusId"); - - b.HasIndex("ShippingStatusId") - .HasDatabaseName("IX_Order_ShippingStatusId"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Order_UserId"); - - b.HasIndex("UserId", "OrderDate") - .HasDatabaseName("IX_Order_User_Date"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("City") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + b.Property("Country") .IsRequired() .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("CreatedAt") + b.Property("CreatedAt") .HasColumnType("datetime2"); b.Property("CreatedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("ModifiedAt") + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") .ValueGeneratedOnAdd() .HasColumnType("datetime2") .HasDefaultValueSql("GETUTCDATE()"); b.Property("ModifiedBy") - .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); b.Property("PostalCode") .IsRequired() @@ -155,193 +121,35 @@ namespace Imprink.Infrastructure.Migrations .HasMaxLength(100) .HasColumnType("nvarchar(100)"); - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderAddress_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderAddress_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderAddress_ModifiedAt"); - - b.HasIndex("OrderId") - .IsUnique() - .HasDatabaseName("IX_OrderAddress_OrderId"); - - b.ToTable("OrderAddresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") + b.Property("UserId") .IsRequired() .HasMaxLength(450) .HasColumnType("nvarchar(450)"); - b.Property("CustomizationDescription") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("CustomizationImageUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductVariantId") - .HasColumnType("uniqueidentifier"); - - b.Property("Quantity") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("TotalPrice") - .HasColumnType("decimal(18,2)"); - - b.Property("UnitPrice") - .HasColumnType("decimal(18,2)"); - b.HasKey("Id"); b.HasIndex("CreatedAt") - .HasDatabaseName("IX_OrderItem_CreatedAt"); + .HasDatabaseName("IX_Address_CreatedAt"); b.HasIndex("CreatedBy") - .HasDatabaseName("IX_OrderItem_CreatedBy"); + .HasDatabaseName("IX_Address_CreatedBy"); b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_OrderItem_ModifiedAt"); + .HasDatabaseName("IX_Address_ModifiedAt"); - b.HasIndex("OrderId") - .HasDatabaseName("IX_OrderItem_OrderId"); + b.HasIndex("UserId") + .HasDatabaseName("IX_Address_UserId"); - b.HasIndex("ProductId") - .HasDatabaseName("IX_OrderItem_ProductId"); + b.HasIndex("UserId", "AddressType") + .HasDatabaseName("IX_Address_User_Type"); - b.HasIndex("ProductVariantId") - .HasDatabaseName("IX_OrderItem_ProductVariantId"); + b.HasIndex("UserId", "IsDefault") + .HasDatabaseName("IX_Address_User_Default"); - b.HasIndex("OrderId", "ProductId") - .HasDatabaseName("IX_OrderItem_Order_Product"); - - b.ToTable("OrderItems"); + b.ToTable("Addresses"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_OrderStatus_Name"); - - b.ToTable("OrderStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Pending" - }, - new - { - Id = 1, - Name = "Processing" - }, - new - { - Id = 2, - Name = "Completed" - }, - new - { - Id = 3, - Name = "Cancelled" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Property("Id") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique() - .HasDatabaseName("IX_ShippingStatus_Name"); - - b.ToTable("ShippingStatuses"); - - b.HasData( - new - { - Id = 0, - Name = "Prepping" - }, - new - { - Id = 1, - Name = "Packaging" - }, - new - { - Id = 2, - Name = "Shipped" - }, - new - { - Id = 3, - Name = "Delivered" - }); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -458,7 +266,273 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("Amount") + .HasColumnType("decimal(18,2)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("CustomizationDescription") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("CustomizationImageUrl") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("MerchantId") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("OrderStatusId") + .HasColumnType("int"); + + b.Property("OriginalImageUrls") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductVariantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("ShippingStatusId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_Order_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_Order_CreatedBy"); + + b.HasIndex("MerchantId") + .HasDatabaseName("IX_Order_MerchantId"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_Order_ModifiedAt"); + + b.HasIndex("OrderDate") + .HasDatabaseName("IX_Order_OrderDate"); + + b.HasIndex("OrderStatusId") + .HasDatabaseName("IX_Order_OrderStatusId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_Order_ProductId"); + + b.HasIndex("ProductVariantId") + .HasDatabaseName("IX_Order_ProductVariantId"); + + b.HasIndex("ShippingStatusId") + .HasDatabaseName("IX_Order_ShippingStatusId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_Order_UserId"); + + b.HasIndex("MerchantId", "OrderDate") + .HasDatabaseName("IX_Order_Merchant_Date"); + + b.HasIndex("ProductId", "OrderDate") + .HasDatabaseName("IX_Order_Product_Date"); + + b.HasIndex("UserId", "OrderDate") + .HasDatabaseName("IX_Order_User_Date"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("NEWID()"); + + b.Property("AddressLine1") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressLine2") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("AddressType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ApartmentNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("BuildingNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Company") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("FirstName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Floor") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Instructions") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("LastName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ModifiedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("ModifiedBy") + .HasMaxLength(450) + .HasColumnType("nvarchar(450)"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PhoneNumber") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PostalCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_OrderAddress_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_OrderAddress_CreatedBy"); + + b.HasIndex("ModifiedAt") + .HasDatabaseName("IX_OrderAddress_ModifiedAt"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderAddress_OrderId"); + + b.ToTable("OrderAddresses"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_OrderStatus_Name"); + + b.ToTable("OrderStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Pending" + }, + new + { + Id = 1, + Name = "Processing" + }, + new + { + Id = 2, + Name = "Completed" + }, + new + { + Id = 3, + Name = "Cancelled" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -542,7 +616,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Products"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -628,100 +702,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("NEWID()"); - - b.Property("AddressType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Country") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.Property("IsDefault") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(false); - - b.Property("ModifiedAt") - .ValueGeneratedOnAdd() - .HasColumnType("datetime2") - .HasDefaultValueSql("GETUTCDATE()"); - - b.Property("ModifiedBy") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.Property("PostalCode") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("State") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Street") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UserId") - .IsRequired() - .HasMaxLength(450) - .HasColumnType("nvarchar(450)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt") - .HasDatabaseName("IX_Address_CreatedAt"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("IX_Address_CreatedBy"); - - b.HasIndex("ModifiedAt") - .HasDatabaseName("IX_Address_ModifiedAt"); - - b.HasIndex("UserId") - .HasDatabaseName("IX_Address_UserId"); - - b.HasIndex("UserId", "AddressType") - .HasDatabaseName("IX_Address_User_Type"); - - b.HasIndex("UserId", "IsDefault") - .HasDatabaseName("IX_Address_User_Default"); - - b.ToTable("Addresses"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Property("Id") .HasColumnType("uniqueidentifier"); @@ -752,7 +733,48 @@ namespace Imprink.Infrastructure.Migrations }); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_ShippingStatus_Name"); + + b.ToTable("ShippingStatuses"); + + b.HasData( + new + { + Id = 0, + Name = "Prepping" + }, + new + { + Id = 1, + Name = "Packaging" + }, + new + { + Id = 2, + Name = "Shipped" + }, + new + { + Id = 3, + Name = "Delivered" + }); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Property("Id") .HasMaxLength(450) @@ -764,7 +786,6 @@ namespace Imprink.Infrastructure.Migrations .HasColumnType("nvarchar(256)"); b.Property("EmailVerified") - .HasMaxLength(100) .HasColumnType("bit"); b.Property("FirstName") @@ -806,7 +827,7 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { b.Property("UserId") .HasMaxLength(450) @@ -826,73 +847,20 @@ namespace Imprink.Infrastructure.Migrations b.ToTable("UserRole"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => + modelBuilder.Entity("Imprink.Domain.Entities.Address", b => { - b.HasOne("Imprink.Domain.Entities.Orders.OrderStatus", "OrderStatus") - .WithMany("Orders") - .HasForeignKey("OrderStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Orders.ShippingStatus", "ShippingStatus") - .WithMany("Orders") - .HasForeignKey("ShippingStatusId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Users.User", "User") - .WithMany("Orders") + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Addresses") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderStatus"); - - b.Navigation("ShippingStatus"); - b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderAddress", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithOne("OrderAddress") - .HasForeignKey("Imprink.Domain.Entities.Orders.OrderAddress", "OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Order"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderItem", b => - { - b.HasOne("Imprink.Domain.Entities.Orders.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Imprink.Domain.Entities.Product.ProductVariant", "ProductVariant") - .WithMany("OrderItems") - .HasForeignKey("ProductVariantId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Order"); - - b.Navigation("Product"); - - b.Navigation("ProductVariant"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => - { - b.HasOne("Imprink.Domain.Entities.Product.Category", "ParentCategory") + b.HasOne("Imprink.Domain.Entities.Category", "ParentCategory") .WithMany("SubCategories") .HasForeignKey("ParentCategoryId") .OnDelete(DeleteBehavior.Restrict); @@ -900,9 +868,69 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("ParentCategory"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.HasOne("Imprink.Domain.Entities.Product.Category", "Category") + b.HasOne("Imprink.Domain.Entities.User", "Merchant") + .WithMany("MerchantOrders") + .HasForeignKey("MerchantId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Imprink.Domain.Entities.OrderStatus", "OrderStatus") + .WithMany("Orders") + .HasForeignKey("OrderStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.Product", "Product") + .WithMany("Orders") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.ProductVariant", "ProductVariant") + .WithMany("Orders") + .HasForeignKey("ProductVariantId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Imprink.Domain.Entities.ShippingStatus", "ShippingStatus") + .WithMany("Orders") + .HasForeignKey("ShippingStatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Imprink.Domain.Entities.User", "User") + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Merchant"); + + b.Navigation("OrderStatus"); + + b.Navigation("Product"); + + b.Navigation("ProductVariant"); + + b.Navigation("ShippingStatus"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderAddress", b => + { + b.HasOne("Imprink.Domain.Entities.Order", "Order") + .WithOne("OrderAddress") + .HasForeignKey("Imprink.Domain.Entities.OrderAddress", "OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.HasOne("Imprink.Domain.Entities.Category", "Category") .WithMany("Products") .HasForeignKey("CategoryId") .OnDelete(DeleteBehavior.SetNull); @@ -910,9 +938,9 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Category"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.HasOne("Imprink.Domain.Entities.Product.Product", "Product") + b.HasOne("Imprink.Domain.Entities.Product", "Product") .WithMany("ProductVariants") .HasForeignKey("ProductId") .OnDelete(DeleteBehavior.Cascade) @@ -921,24 +949,15 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("Product"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Address", b => + modelBuilder.Entity("Imprink.Domain.Entities.UserRole", b => { - b.HasOne("Imprink.Domain.Entities.Users.User", null) - .WithMany("Addresses") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Users.UserRole", b => - { - b.HasOne("Imprink.Domain.Entities.Users.Role", "Role") + b.HasOne("Imprink.Domain.Entities.Role", "Role") .WithMany("UserRoles") .HasForeignKey("RoleId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.HasOne("Imprink.Domain.Entities.Users.User", "User") + b.HasOne("Imprink.Domain.Entities.User", "User") .WithMany("UserRoles") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -949,52 +968,52 @@ namespace Imprink.Infrastructure.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Orders.Order", b => - { - b.Navigation("OrderAddress") - .IsRequired(); - - b.Navigation("OrderItems"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.OrderStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Orders.ShippingStatus", b => - { - b.Navigation("Orders"); - }); - - modelBuilder.Entity("Imprink.Domain.Entities.Product.Category", b => + modelBuilder.Entity("Imprink.Domain.Entities.Category", b => { b.Navigation("Products"); b.Navigation("SubCategories"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.Product", b => + modelBuilder.Entity("Imprink.Domain.Entities.Order", b => { - b.Navigation("OrderItems"); + b.Navigation("OrderAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.OrderStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.Product", b => + { + b.Navigation("Orders"); b.Navigation("ProductVariants"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Product.ProductVariant", b => + modelBuilder.Entity("Imprink.Domain.Entities.ProductVariant", b => { - b.Navigation("OrderItems"); + b.Navigation("Orders"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.Role", b => + modelBuilder.Entity("Imprink.Domain.Entities.Role", b => { b.Navigation("UserRoles"); }); - modelBuilder.Entity("Imprink.Domain.Entities.Users.User", b => + modelBuilder.Entity("Imprink.Domain.Entities.ShippingStatus", b => + { + b.Navigation("Orders"); + }); + + modelBuilder.Entity("Imprink.Domain.Entities.User", b => { b.Navigation("Addresses"); + b.Navigation("MerchantOrders"); + b.Navigation("Orders"); b.Navigation("UserRoles"); diff --git a/src/Imprink.Infrastructure/Repositories/AddressRepository.cs b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs index e30e420..10fb3e0 100644 --- a/src/Imprink.Infrastructure/Repositories/AddressRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/AddressRepository.cs @@ -25,7 +25,7 @@ public class AddressRepository(ApplicationDbContext context) : IAddressRepositor .ToListAsync(cancellationToken); } - public async Task GetDefaultByUserIdAsync(string userId, CancellationToken cancellationToken = default) + public async Task GetDefaultByUserIdAsync(string? userId, CancellationToken cancellationToken = default) { return await context.Addresses .FirstOrDefaultAsync(a => a.UserId == userId && a.IsDefault && a.IsActive, cancellationToken); @@ -103,7 +103,7 @@ public class AddressRepository(ApplicationDbContext context) : IAddressRepositor return true; } - public async Task SetDefaultAddressAsync(string userId, Guid addressId, CancellationToken cancellationToken = default) + public async Task SetDefaultAddressAsync(string? userId, Guid addressId, CancellationToken cancellationToken = default) { await UnsetDefaultAddressesAsync(userId, cancellationToken); @@ -154,7 +154,7 @@ public class AddressRepository(ApplicationDbContext context) : IAddressRepositor .AnyAsync(a => a.Id == id && a.UserId == userId, cancellationToken); } - private async Task UnsetDefaultAddressesAsync(string userId, CancellationToken cancellationToken = default) + private async Task UnsetDefaultAddressesAsync(string? userId, CancellationToken cancellationToken = default) { var defaultAddresses = await context.Addresses .Where(a => a.UserId == userId && a.IsDefault) diff --git a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs index 1a11f67..d8e00b7 100644 --- a/src/Imprink.Infrastructure/Repositories/OrderRepository.cs +++ b/src/Imprink.Infrastructure/Repositories/OrderRepository.cs @@ -25,12 +25,6 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .FirstOrDefaultAsync(o => o.Id == id, cancellationToken); } - public async Task GetByOrderNumberAsync(string orderNumber, CancellationToken cancellationToken = default) - { - return await context.Orders - .FirstOrDefaultAsync(o => o.OrderNumber == orderNumber, cancellationToken); - } - public async Task> GetByUserIdAsync(string userId, CancellationToken cancellationToken = default) { return await context.Orders @@ -134,37 +128,6 @@ public class OrderRepository(ApplicationDbContext context) : IOrderRepository .AnyAsync(o => o.Id == id, cancellationToken); } - public async Task IsOrderNumberUniqueAsync(string orderNumber, CancellationToken cancellationToken = default) - { - return !await context.Orders - .AnyAsync(o => o.OrderNumber == orderNumber, cancellationToken); - } - - public async Task IsOrderNumberUniqueAsync(string orderNumber, Guid excludeOrderId, CancellationToken cancellationToken = default) - { - return !await context.Orders - .AnyAsync(o => o.OrderNumber == orderNumber && o.Id != excludeOrderId, cancellationToken); - } - - public async Task GenerateOrderNumberAsync(CancellationToken cancellationToken = default) - { - string orderNumber; - bool isUnique; - - do - { - // Generate order number format: ORD-YYYYMMDD-XXXXXX (where X is random) - var datePart = DateTime.UtcNow.ToString("yyyyMMdd"); - var randomPart = Random.Shared.Next(100000, 999999).ToString(); - orderNumber = $"ORD-{datePart}-{randomPart}"; - - isUnique = await IsOrderNumberUniqueAsync(orderNumber, cancellationToken); - } - while (!isUnique); - - return orderNumber; - } - public async Task UpdateStatusAsync(Guid orderId, int statusId, CancellationToken cancellationToken = default) { var order = await context.Orders diff --git a/src/Imprink.Infrastructure/UnitOfWork.cs b/src/Imprink.Infrastructure/UnitOfWork.cs index 80e3472..3025afd 100644 --- a/src/Imprink.Infrastructure/UnitOfWork.cs +++ b/src/Imprink.Infrastructure/UnitOfWork.cs @@ -52,7 +52,6 @@ public class UnitOfWork( try { var result = await operation(); - await SaveAsync(cancellationToken); await CommitTransactionAsync(cancellationToken); return result; } @@ -69,7 +68,6 @@ public class UnitOfWork( try { await operation(); - await SaveAsync(cancellationToken); await CommitTransactionAsync(cancellationToken); } catch diff --git a/src/Imprink.WebApi/Controllers/AddressesController.cs b/src/Imprink.WebApi/Controllers/AddressesController.cs new file mode 100644 index 0000000..bac0cfb --- /dev/null +++ b/src/Imprink.WebApi/Controllers/AddressesController.cs @@ -0,0 +1,60 @@ +using Imprink.Application.Commands.Addresses; +using Imprink.Application.Dtos; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Imprink.WebApi.Controllers; + +[ApiController] +[Route("/api/addresses")] +public class AddressesController(IMediator mediator) : ControllerBase +{ + + [HttpGet("{id:guid}")] + [Authorize] + public async Task> GetAddressById( + Guid id, + [FromQuery] string? userId = null, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetAddressByIdQuery + { + Id = id, + UserId = userId + }, cancellationToken); + + if (result == null) + return NotFound(); + + return Ok(result); + } + + [HttpGet("user/{userId}")] + [Authorize] + public async Task>> GetAddressesByUserId( + string userId, + [FromQuery] bool activeOnly = false, + [FromQuery] string? addressType = null, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetAddressesByUserIdQuery + { + UserId = userId, + ActiveOnly = activeOnly, + AddressType = addressType + }, cancellationToken); + + return Ok(result); + } + + [HttpPost] + [Authorize] + public async Task> CreateAddress( + [FromBody] CreateAddressCommand command, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(command, cancellationToken); + return CreatedAtAction(nameof(GetAddressById), new { id = result.Id }, result); + } +} \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/OrdersController.cs b/src/Imprink.WebApi/Controllers/OrdersController.cs new file mode 100644 index 0000000..da9366b --- /dev/null +++ b/src/Imprink.WebApi/Controllers/OrdersController.cs @@ -0,0 +1,74 @@ +using Imprink.Application.Commands.Orders; +using Imprink.Application.Dtos; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Imprink.WebApi.Controllers; + +[ApiController] +[Route("/api/orders")] +public class OrdersController(IMediator mediator) : ControllerBase +{ + + [HttpGet("{id:guid}")] + [Authorize] + public async Task> GetOrderById( + Guid id, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrderByIdQuery + { + Id = id, + IncludeDetails = includeDetails + }, cancellationToken); + + if (result == null) + return NotFound(); + + return Ok(result); + } + + [HttpGet("user/{userId}")] + [Authorize] + public async Task>> GetOrdersByUserId( + string userId, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrdersByUserIdQuery + { + UserId = userId, + IncludeDetails = includeDetails + }, cancellationToken); + + return Ok(result); + } + + [HttpGet("merchant/{merchantId}")] + [Authorize(Roles = "Admin,Merchant")] + public async Task>> GetOrdersByMerchantId( + string merchantId, + [FromQuery] bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(new GetOrdersByMerchantIdQuery + { + MerchantId = merchantId, + IncludeDetails = includeDetails + }, cancellationToken); + + return Ok(result); + } + + [HttpPost] + [Authorize] + public async Task> CreateOrder( + [FromBody] CreateOrderCommand command, + CancellationToken cancellationToken = default) + { + var result = await mediator.Send(command, cancellationToken); + return CreatedAtAction(nameof(GetOrderById), new { id = result.Id }, result); + } +} \ No newline at end of file -- 2.49.1 From 62341c7f87250fec23e5e142297d62a8525901d3 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 00:28:11 +0300 Subject: [PATCH 4/8] Ugh --- .../Commands/Addresses/CreateAddressHandler.cs | 2 -- .../Commands/Orders/CreateOrderHandler.cs | 18 ++++++++++++------ .../Commands/Orders/GetOrderByIdHandler.cs | 2 +- .../Orders/GetOrdersByMerchantIdHandler.cs | 2 +- .../Orders/GetOrdersByUserIdHandler.cs | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs index 83de43c..330559e 100644 --- a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs @@ -42,8 +42,6 @@ public class CreateAddressHandler( var address = mapper.Map
(request); address.UserId = userService.GetCurrentUserId()!; - address.CreatedAt = DateTime.UtcNow; - address.ModifiedAt = DateTime.UtcNow; if (address.IsDefault) { diff --git a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs index 55b09f6..25c41c4 100644 --- a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs +++ b/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs @@ -9,10 +9,9 @@ namespace Imprink.Application.Commands.Orders; public class CreateOrderCommand : IRequest { - public decimal Amount { get; set; } public int Quantity { get; set; } public Guid ProductId { get; set; } - public Guid? ProductVariantId { get; set; } + public Guid ProductVariantId { get; set; } public string? Notes { get; set; } public string? MerchantId { get; set; } public string? ComposingImageUrl { get; set; } @@ -41,6 +40,13 @@ public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserServ order.OrderStatusId = 0; order.ShippingStatusId = 0; + var variant = uw.ProductVariantRepository.GetByIdAsync(request.ProductVariantId, cancellationToken).Result; + if (variant == null) + throw new NotFoundException("Product variant not found"); + + order.Amount = variant.Price * request.Quantity; + + var createdOrder = await uw.OrderRepository.AddAsync(order, cancellationToken); var orderAddress = new OrderAddress @@ -68,10 +74,10 @@ public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserServ createdOrder.Product = (await uw.ProductRepository.GetByIdAsync(createdOrder.ProductId, cancellationToken))!; - if (createdOrder.ProductVariantId.HasValue) - { - createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); - } + if (!createdOrder.ProductVariantId.HasValue) + throw new NotFoundException("Product variant not found"); + + createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); await uw.SaveAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs index be46ab9..8e4724f 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Orders; public class GetOrderByIdQuery : IRequest { public Guid Id { get; set; } - public bool IncludeDetails { get; set; } = false; + public bool IncludeDetails { get; set; } } public class GetOrderByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs index b34ea92..bb62a68 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Orders; public class GetOrdersByMerchantIdQuery : IRequest> { public string MerchantId { get; set; } = null!; - public bool IncludeDetails { get; set; } = false; + public bool IncludeDetails { get; set; } } public class GetOrdersByMerchantIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs index 6f778a3..a73a700 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Orders; public class GetOrdersByUserIdQuery : IRequest> { public string UserId { get; set; } = null!; - public bool IncludeDetails { get; set; } = false; + public bool IncludeDetails { get; set; } } public class GetOrdersByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> -- 2.49.1 From 3cdfbf895429f07d4e51be196a0c1e84f32f7a79 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 00:51:24 +0300 Subject: [PATCH 5/8] Cleanup in handlers --- .../Addresses/CreateAddressHandler.cs | 15 +++++---- .../Addresses/GetAddressByIdHandler.cs | 31 ------------------ .../Addresses/GetAddressesByUserIdHandler.cs | 17 +++++++--- .../Addresses/GetMyAddressesHandler.cs | 26 +++++++++++++++ .../Categories/CreateCategoryHandler.cs | 12 +++++-- .../Categories/DeleteCategoryHandler.cs | 12 +++++-- .../Categories/GetCategoriesHandler.cs | 7 ++-- .../Categories/UpdateCategoryHandler.cs | 15 ++++++--- .../Commands/Orders/CreateOrderHandler.cs | 32 +++++++++++++------ .../Commands/Orders/GetOrderByIdHandler.cs | 9 ++++-- .../Orders/GetOrdersByMerchantIdHandler.cs | 15 ++++++--- .../Orders/GetOrdersByUserIdHandler.cs | 15 ++++++--- .../CreateProductVariantHandler.cs | 11 +++++-- .../DeleteProductVariantHandler.cs | 11 +++++-- .../GetProductVariantsHandler.cs | 18 +++++++---- .../UpdateProductVariantHandler.cs | 14 +++++--- .../Commands/Products/CreateProductHandler.cs | 9 ++++-- .../Commands/Products/DeleteProductHandler.cs | 11 +++++-- .../Commands/Products/GetProductsHandler.cs | 11 +++++-- .../Commands/Products/UpdateProductCommand.cs | 14 +++++--- .../Commands/Users/DeleteUserRoleHandler.cs | 15 ++++++--- .../Commands/Users/GetAllRolesHandler.cs | 11 +++++-- .../Commands/Users/GetUserRolesHandler.cs | 12 +++++-- .../Commands/Users/SetUserFullNameHandler.cs | 13 ++++++-- .../Commands/Users/SetUserPhoneHandler.cs | 13 ++++++-- .../Commands/Users/SetUserRoleHandler.cs | 12 +++++-- .../Commands/Users/SyncUserHandler.cs | 12 +++++-- .../Services/ICurrentUserService.cs | 2 +- .../Controllers/AddressesController.cs | 21 +++--------- 29 files changed, 274 insertions(+), 142 deletions(-) delete mode 100644 src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs create mode 100644 src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs index 330559e..9e9a94c 100644 --- a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs @@ -3,7 +3,6 @@ using Imprink.Application.Dtos; using Imprink.Application.Services; using Imprink.Domain.Entities; using MediatR; -using Microsoft.Extensions.Logging; namespace Imprink.Application.Commands.Addresses; @@ -31,21 +30,23 @@ public class CreateAddressCommand : IRequest public class CreateAddressHandler( IUnitOfWork uw, IMapper mapper, - ICurrentUserService userService, - ILogger logger) + ICurrentUserService userService) : IRequestHandler { - public async Task Handle(CreateAddressCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateAddressCommand request, + CancellationToken cancellationToken) { return await uw.TransactAsync(async () => { var address = mapper.Map
(request); - - address.UserId = userService.GetCurrentUserId()!; + address.UserId = userService.GetCurrentUserId(); if (address.IsDefault) { - var currentDefault = await uw.AddressRepository.GetDefaultByUserIdAsync(address.UserId, cancellationToken); + var currentDefault = await uw.AddressRepository + .GetDefaultByUserIdAsync(address.UserId, cancellationToken); + if (currentDefault != null) { currentDefault.IsDefault = false; diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs b/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs deleted file mode 100644 index 3d14552..0000000 --- a/src/Imprink.Application/Commands/Addresses/GetAddressByIdHandler.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AutoMapper; -using Imprink.Application.Dtos; -using Imprink.Domain.Entities; -using MediatR; - -namespace Imprink.Application.Commands.Addresses; - -public class GetAddressByIdQuery : IRequest -{ - public Guid Id { get; set; } - public string? UserId { get; set; } -} - -public class GetAddressByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler -{ - public async Task Handle(GetAddressByIdQuery request, CancellationToken cancellationToken) - { - Address? address; - - if (!string.IsNullOrEmpty(request.UserId)) - { - address = await uw.AddressRepository.GetByIdAndUserIdAsync(request.Id, request.UserId, cancellationToken); - } - else - { - address = await uw.AddressRepository.GetByIdAsync(request.Id, cancellationToken); - } - - return address != null ? mapper.Map(address) : null; - } -} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs index 9d151cc..5816518 100644 --- a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs @@ -8,23 +8,30 @@ namespace Imprink.Application.Commands.Addresses; public class GetAddressesByUserIdQuery : IRequest> { public string UserId { get; set; } = null!; - public bool ActiveOnly { get; set; } = false; + public bool ActiveOnly { get; set; } public string? AddressType { get; set; } } -public class GetAddressesByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +public class GetAddressesByUserIdHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> { - public async Task> Handle(GetAddressesByUserIdQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetAddressesByUserIdQuery request, + CancellationToken cancellationToken) { IEnumerable
addresses; if (!string.IsNullOrEmpty(request.AddressType)) { - addresses = await uw.AddressRepository.GetByUserIdAndTypeAsync(request.UserId, request.AddressType, cancellationToken); + addresses = await uw.AddressRepository + .GetByUserIdAndTypeAsync(request.UserId, request.AddressType, cancellationToken); } else if (request.ActiveOnly) { - addresses = await uw.AddressRepository.GetActiveByUserIdAsync(request.UserId, cancellationToken); + addresses = await uw.AddressRepository + .GetActiveByUserIdAsync(request.UserId, cancellationToken); } else { diff --git a/src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs b/src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs new file mode 100644 index 0000000..3f7a565 --- /dev/null +++ b/src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using Imprink.Application.Dtos; +using Imprink.Application.Services; +using Imprink.Domain.Entities; +using MediatR; + +namespace Imprink.Application.Commands.Addresses; + +public class GetMyAddressesQuery : IRequest>; + +public class GetMyAddressesHandler( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler> +{ + public async Task> Handle( + GetMyAddressesQuery request, + CancellationToken cancellationToken) + { + IEnumerable addresses = await uw.AddressRepository + .GetByUserIdAsync(userService.GetCurrentUserId(), cancellationToken); + + return mapper.Map>(addresses); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs index 310578a..d690cc4 100644 --- a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs @@ -14,9 +14,13 @@ public class CreateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class CreateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class CreateCategoryHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(CreateCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); @@ -32,7 +36,9 @@ public class CreateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler public Guid Id { get; init; } } -public class DeleteCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class DeleteCategoryHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(DeleteCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var exists = await unitOfWork.CategoryRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await unitOfWork.CategoryRepository + .ExistsAsync(request.Id, cancellationToken); + if (!exists) { await unitOfWork.RollbackTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs b/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs index b606dbe..1e5fcbd 100644 --- a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs +++ b/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs @@ -10,10 +10,13 @@ public class GetCategoriesQuery : IRequest> public bool RootCategoriesOnly { get; set; } = false; } -public class GetCategoriesHandler(IUnitOfWork unitOfWork) +public class GetCategoriesHandler( + IUnitOfWork unitOfWork) : IRequestHandler> { - public async Task> Handle(GetCategoriesQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetCategoriesQuery request, + CancellationToken cancellationToken) { IEnumerable categories; diff --git a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs index f9f5f23..975d3c5 100644 --- a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs @@ -15,15 +15,20 @@ public class UpdateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class UpdateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class UpdateCategoryHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(UpdateCategoryCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateCategoryCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingCategory = await unitOfWork.CategoryRepository.GetByIdAsync(request.Id, cancellationToken); + var existingCategory = await unitOfWork.CategoryRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingCategory == null) { @@ -37,7 +42,9 @@ public class UpdateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler public Guid AddressId { get; set; } } -public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler +public class CreateOrderHandler( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler { - public async Task Handle(CreateOrderCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateOrderCommand request, + CancellationToken cancellationToken) { return await uw.TransactAsync(async () => { - var sourceAddress = await uw.AddressRepository.GetByIdAndUserIdAsync(request.AddressId, userService.GetCurrentUserId()!, cancellationToken); + var userId = userService.GetCurrentUserId()!; + + var sourceAddress = await uw.AddressRepository + .GetByIdAndUserIdAsync(request.AddressId, userId, cancellationToken); + if (sourceAddress == null) - { - throw new NotFoundException($"Address with ID {request.AddressId} not found for user {userService.GetCurrentUserId()!}"); - } + throw new NotFoundException($"Address {request.AddressId} not found for {userId}"); var order = mapper.Map(request); - order.UserId = userService.GetCurrentUserId()!; + order.UserId = userService.GetCurrentUserId(); order.OrderDate = DateTime.UtcNow; order.OrderStatusId = 0; order.ShippingStatusId = 0; - var variant = uw.ProductVariantRepository.GetByIdAsync(request.ProductVariantId, cancellationToken).Result; + var variant = uw.ProductVariantRepository + .GetByIdAsync(request.ProductVariantId, cancellationToken).Result; + if (variant == null) throw new NotFoundException("Product variant not found"); @@ -72,12 +82,14 @@ public class CreateOrderHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserServ await uw.OrderAddressRepository.AddAsync(orderAddress, cancellationToken); - createdOrder.Product = (await uw.ProductRepository.GetByIdAsync(createdOrder.ProductId, cancellationToken))!; + createdOrder.Product = (await uw.ProductRepository + .GetByIdAsync(createdOrder.ProductId, cancellationToken))!; if (!createdOrder.ProductVariantId.HasValue) throw new NotFoundException("Product variant not found"); - createdOrder.ProductVariant = await uw.ProductVariantRepository.GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); + createdOrder.ProductVariant = await uw.ProductVariantRepository + .GetByIdAsync(createdOrder.ProductVariantId.Value, cancellationToken); await uw.SaveAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs index 8e4724f..f9d0770 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs @@ -11,9 +11,14 @@ public class GetOrderByIdQuery : IRequest public bool IncludeDetails { get; set; } } -public class GetOrderByIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class GetOrderByIdHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(GetOrderByIdQuery request, CancellationToken cancellationToken) + public async Task Handle( + GetOrderByIdQuery request, + CancellationToken cancellationToken) { Order? order; diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs index bb62a68..93b30ac 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs @@ -11,19 +11,26 @@ public class GetOrdersByMerchantIdQuery : IRequest> public bool IncludeDetails { get; set; } } -public class GetOrdersByMerchantIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +public class GetOrdersByMerchantIdHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> { - public async Task> Handle(GetOrdersByMerchantIdQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetOrdersByMerchantIdQuery request, + CancellationToken cancellationToken) { IEnumerable orders; if (request.IncludeDetails) { - orders = await uw.OrderRepository.GetByMerchantIdWithDetailsAsync(request.MerchantId, cancellationToken); + orders = await uw.OrderRepository + .GetByMerchantIdWithDetailsAsync(request.MerchantId, cancellationToken); } else { - orders = await uw.OrderRepository.GetByMerchantIdAsync(request.MerchantId, cancellationToken); + orders = await uw.OrderRepository + .GetByMerchantIdAsync(request.MerchantId, cancellationToken); } return mapper.Map>(orders); diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs index a73a700..41dd354 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs @@ -11,19 +11,26 @@ public class GetOrdersByUserIdQuery : IRequest> public bool IncludeDetails { get; set; } } -public class GetOrdersByUserIdHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler> +public class GetOrdersByUserIdHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> { - public async Task> Handle(GetOrdersByUserIdQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetOrdersByUserIdQuery request, + CancellationToken cancellationToken) { IEnumerable orders; if (request.IncludeDetails) { - orders = await uw.OrderRepository.GetByUserIdWithDetailsAsync(request.UserId, cancellationToken); + orders = await uw.OrderRepository + .GetByUserIdWithDetailsAsync(request.UserId, cancellationToken); } else { - orders = await uw.OrderRepository.GetByUserIdAsync(request.UserId, cancellationToken); + orders = await uw.OrderRepository + .GetByUserIdAsync(request.UserId, cancellationToken); } return mapper.Map>(orders); diff --git a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs index 0ab7aa7..1444882 100644 --- a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs @@ -17,10 +17,14 @@ public class CreateProductVariantCommand : IRequest public bool IsActive { get; set; } = true; } -public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) +public class CreateProductVariantHandler( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler { - public async Task Handle(CreateProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); @@ -30,7 +34,8 @@ public class CreateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) productVariant.Product = null!; - var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken); + var createdVariant = await unitOfWork.ProductVariantRepository + .AddAsync(productVariant, cancellationToken); await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs index 8e6a2ea..d461975 100644 --- a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs @@ -7,15 +7,20 @@ public class DeleteProductVariantCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class DeleteProductVariantHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(DeleteProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await unitOfWork.ProductVariantRepository + .ExistsAsync(request.Id, cancellationToken); if (!exists) { diff --git a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs b/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs index 5e76ae2..1e56414 100644 --- a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs @@ -2,7 +2,6 @@ using AutoMapper; using Imprink.Application.Dtos; using Imprink.Domain.Entities; using MediatR; -using Microsoft.Extensions.Logging; namespace Imprink.Application.Commands.ProductVariants; @@ -13,10 +12,14 @@ public class GetProductVariantsQuery : IRequest> public bool InStockOnly { get; set; } = false; } -public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, ILogger logger) +public class GetProductVariantsHandler( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler> { - public async Task> Handle(GetProductVariantsQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetProductVariantsQuery request, + CancellationToken cancellationToken) { IEnumerable variants; @@ -24,15 +27,18 @@ public class GetProductVariantsHandler(IUnitOfWork unitOfWork, IMapper mapper, I { if (request.InStockOnly) { - variants = await unitOfWork.ProductVariantRepository.GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetInStockByProductIdAsync(request.ProductId.Value, cancellationToken); } else if (request.IsActive.HasValue && request.IsActive.Value) { - variants = await unitOfWork.ProductVariantRepository.GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetActiveByProductIdAsync(request.ProductId.Value, cancellationToken); } else { - variants = await unitOfWork.ProductVariantRepository.GetByProductIdAsync(request.ProductId.Value, cancellationToken); + variants = await unitOfWork.ProductVariantRepository + .GetByProductIdAsync(request.ProductId.Value, cancellationToken); } } else diff --git a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs index 0c88be3..e45232c 100644 --- a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs @@ -18,23 +18,29 @@ public class UpdateProductVariantCommand : IRequest public bool IsActive { get; set; } } -public class UpdateProductVariantHandler(IUnitOfWork unitOfWork, IMapper mapper) +public class UpdateProductVariantHandler( + IUnitOfWork unitOfWork, + IMapper mapper) : IRequestHandler { - public async Task Handle(UpdateProductVariantCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateProductVariantCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingVariant = await unitOfWork.ProductVariantRepository.GetByIdAsync(request.Id, cancellationToken); + var existingVariant = await unitOfWork.ProductVariantRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingVariant == null) throw new NotFoundException($"Product variant with ID {request.Id} not found."); mapper.Map(request, existingVariant); - var updatedVariant = await unitOfWork.ProductVariantRepository.UpdateAsync(existingVariant, cancellationToken); + var updatedVariant = await unitOfWork.ProductVariantRepository + .UpdateAsync(existingVariant, cancellationToken); await unitOfWork.SaveAsync(cancellationToken); await unitOfWork.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs b/src/Imprink.Application/Commands/Products/CreateProductHandler.cs index ac12e05..7b93c3c 100644 --- a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/CreateProductHandler.cs @@ -16,9 +16,14 @@ public class CreateProductCommand : IRequest public Guid? CategoryId { get; set; } } -public class CreateProductHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class CreateProductHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(CreateProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + CreateProductCommand request, + CancellationToken cancellationToken) { return await uw.TransactAsync(async () => { diff --git a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs b/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs index 2e6c326..c839fad 100644 --- a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs @@ -8,13 +8,18 @@ public class DeleteProductCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductHandler(IUnitOfWork uw) : IRequestHandler +public class DeleteProductHandler( + IUnitOfWork uw) + : IRequestHandler { - public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteProductCommand request, + CancellationToken cancellationToken) { await uw.TransactAsync(async () => { - var exists = await uw.ProductRepository.ExistsAsync(request.Id, cancellationToken); + var exists = await uw.ProductRepository + .ExistsAsync(request.Id, cancellationToken); if (!exists) { diff --git a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs b/src/Imprink.Application/Commands/Products/GetProductsHandler.cs index 4e65018..0ef998d 100644 --- a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs +++ b/src/Imprink.Application/Commands/Products/GetProductsHandler.cs @@ -9,11 +9,16 @@ public class GetProductsQuery : IRequest> public ProductFilterParameters FilterParameters { get; set; } = new(); } -public class GetProductsHandler(IUnitOfWork unitOfWork) : IRequestHandler> +public class GetProductsHandler( + IUnitOfWork unitOfWork) + : IRequestHandler> { - public async Task> Handle(GetProductsQuery request, CancellationToken cancellationToken) + public async Task> Handle( + GetProductsQuery request, + CancellationToken cancellationToken) { - var pagedResult = await unitOfWork.ProductRepository.GetPagedAsync(request.FilterParameters, cancellationToken); + var pagedResult = await unitOfWork.ProductRepository + .GetPagedAsync(request.FilterParameters, cancellationToken); var productDtos = pagedResult.Items.Select(p => new ProductDto { diff --git a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs b/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs index 06f1313..c60cbb6 100644 --- a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs +++ b/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs @@ -16,15 +16,20 @@ public class UpdateProductCommand : IRequest public Guid? CategoryId { get; set; } } -public class UpdateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler +public class UpdateProductHandler( + IUnitOfWork unitOfWork) + : IRequestHandler { - public async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken) + public async Task Handle( + UpdateProductCommand request, + CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); try { - var existingProduct = await unitOfWork.ProductRepository.GetByIdAsync(request.Id, cancellationToken); + var existingProduct = await unitOfWork.ProductRepository + .GetByIdAsync(request.Id, cancellationToken); if (existingProduct == null) throw new NotFoundException($"Product with ID {request.Id} not found."); @@ -37,7 +42,8 @@ public class UpdateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler; -public class DeleteUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class DeleteUserRoleHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(DeleteUserRoleCommand request, CancellationToken cancellationToken) + public async Task Handle( + DeleteUserRoleCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -18,12 +23,14 @@ public class DeleteUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHan if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) throw new NotFoundException("User with ID: " + request.Sub + " does not exist."); - var existingUserRole = await uw.UserRoleRepository.GetUserRoleAsync(request.Sub, request.RoleId, cancellationToken); + var existingUserRole = await uw.UserRoleRepository + .GetUserRoleAsync(request.Sub, request.RoleId, cancellationToken); if (existingUserRole == null) throw new NotFoundException($"User role not found for user {request.Sub} and role {request.RoleId}"); - var removedRole = await uw.UserRoleRepository.RemoveUserRoleAsync(existingUserRole, cancellationToken); + var removedRole = await uw.UserRoleRepository + .RemoveUserRoleAsync(existingUserRole, cancellationToken); await uw.SaveAsync(cancellationToken); await uw.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs index ec1661b..ec4d093 100644 --- a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs +++ b/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs @@ -6,11 +6,16 @@ namespace Imprink.Application.Commands.Users; public record GetAllRolesCommand : IRequest>; -public class GetAllRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler> +public class GetAllRolesHandler( + IUnitOfWork uw, + IMapper mapper): IRequestHandler> { - public async Task> Handle(GetAllRolesCommand request, CancellationToken cancellationToken) + public async Task> Handle( + GetAllRolesCommand request, + CancellationToken cancellationToken) { - var roles = await uw.RoleRepository.GetAllRolesAsync(cancellationToken); + var roles = await uw.RoleRepository + .GetAllRolesAsync(cancellationToken); return mapper.Map>(roles); } diff --git a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs index ea3f8d8..5517ee0 100644 --- a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs +++ b/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs @@ -7,14 +7,20 @@ namespace Imprink.Application.Commands.Users; public record GetUserRolesCommand(string Sub) : IRequest>; -public class GetUserRolesHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler> +public class GetUserRolesHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler> { - public async Task> Handle(GetUserRolesCommand request, CancellationToken cancellationToken) + public async Task> Handle( + GetUserRolesCommand request, + CancellationToken cancellationToken) { if (!await uw.UserRepository.UserExistsAsync(request.Sub, cancellationToken)) throw new NotFoundException("User with ID: " + request.Sub + " does not exist."); - var roles = await uw.UserRoleRepository.GetUserRolesAsync(request.Sub, cancellationToken); + var roles = await uw.UserRoleRepository + .GetUserRolesAsync(request.Sub, cancellationToken); return mapper.Map>(roles); } diff --git a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs b/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs index 11c9597..0f4a1ca 100644 --- a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs @@ -8,9 +8,15 @@ namespace Imprink.Application.Commands.Users; public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest; -public class SetUserFullNameHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler +public class SetUserFullNameHandler( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler { - public async Task Handle(SetUserFullNameCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserFullNameCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -21,7 +27,8 @@ public class SetUserFullNameHandler(IUnitOfWork uw, IMapper mapper, ICurrentUser if (currentUser == null) throw new NotFoundException("User token could not be accessed."); - var user = await uw.UserRepository.SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken); + var user = await uw.UserRepository + .SetUserFullNameAsync(currentUser, request.FirstName, request.LastName, cancellationToken); if (user == null) throw new DataUpdateException("User name could not be updated."); diff --git a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs b/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs index ac7bc68..f3876e7 100644 --- a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs @@ -8,9 +8,15 @@ namespace Imprink.Application.Commands.Users; public record SetUserPhoneCommand(string PhoneNumber) : IRequest; -public class SetUserPhoneHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) : IRequestHandler +public class SetUserPhoneHandler( + IUnitOfWork uw, + IMapper mapper, + ICurrentUserService userService) + : IRequestHandler { - public async Task Handle(SetUserPhoneCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserPhoneCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -21,7 +27,8 @@ public class SetUserPhoneHandler(IUnitOfWork uw, IMapper mapper, ICurrentUserSer if (currentUser == null) throw new NotFoundException("User token could not be accessed."); - var user = await uw.UserRepository.SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken); + var user = await uw.UserRepository + .SetUserPhoneAsync(currentUser, request.PhoneNumber, cancellationToken); if (user == null) throw new DataUpdateException("User phone could not be updated."); diff --git a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs b/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs index 7c265f0..e826982 100644 --- a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs @@ -8,9 +8,14 @@ namespace Imprink.Application.Commands.Users; public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest; -public class SetUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandler +public class SetUserRoleHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(SetUserRoleCommand request, CancellationToken cancellationToken) + public async Task Handle( + SetUserRoleCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); @@ -25,7 +30,8 @@ public class SetUserRoleHandler(IUnitOfWork uw, IMapper mapper) : IRequestHandle RoleId = request.RoleId }; - var addedRole = await uw.UserRoleRepository.AddUserRoleAsync(userRole, cancellationToken); + var addedRole = await uw.UserRoleRepository + .AddUserRoleAsync(userRole, cancellationToken); await uw.SaveAsync(cancellationToken); await uw.CommitTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs b/src/Imprink.Application/Commands/Users/SyncUserHandler.cs index a304b27..e30da35 100644 --- a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs +++ b/src/Imprink.Application/Commands/Users/SyncUserHandler.cs @@ -7,15 +7,21 @@ namespace Imprink.Application.Commands.Users; public record SyncUserCommand(Auth0User User) : IRequest; -public class SyncUserHandler(IUnitOfWork uw, IMapper mapper): IRequestHandler +public class SyncUserHandler( + IUnitOfWork uw, + IMapper mapper) + : IRequestHandler { - public async Task Handle(SyncUserCommand request, CancellationToken cancellationToken) + public async Task Handle( + SyncUserCommand request, + CancellationToken cancellationToken) { await uw.BeginTransactionAsync(cancellationToken); try { - var user = await uw.UserRepository.UpdateOrCreateUserAsync(request.User, cancellationToken); + var user = await uw.UserRepository + .UpdateOrCreateUserAsync(request.User, cancellationToken); if (user == null) throw new Exception("User exists but could not be updated"); diff --git a/src/Imprink.Application/Services/ICurrentUserService.cs b/src/Imprink.Application/Services/ICurrentUserService.cs index 83bbe41..b4b9c4a 100644 --- a/src/Imprink.Application/Services/ICurrentUserService.cs +++ b/src/Imprink.Application/Services/ICurrentUserService.cs @@ -2,5 +2,5 @@ namespace Imprink.Application.Services; public interface ICurrentUserService { - string? GetCurrentUserId(); + string GetCurrentUserId(); } \ No newline at end of file diff --git a/src/Imprink.WebApi/Controllers/AddressesController.cs b/src/Imprink.WebApi/Controllers/AddressesController.cs index bac0cfb..79e3a39 100644 --- a/src/Imprink.WebApi/Controllers/AddressesController.cs +++ b/src/Imprink.WebApi/Controllers/AddressesController.cs @@ -11,27 +11,16 @@ namespace Imprink.WebApi.Controllers; public class AddressesController(IMediator mediator) : ControllerBase { - [HttpGet("{id:guid}")] + [HttpGet("me")] [Authorize] - public async Task> GetAddressById( - Guid id, - [FromQuery] string? userId = null, - CancellationToken cancellationToken = default) + public async Task>> GetMyAddresses(CancellationToken cancellationToken = default) { - var result = await mediator.Send(new GetAddressByIdQuery - { - Id = id, - UserId = userId - }, cancellationToken); - - if (result == null) - return NotFound(); - + var result = await mediator.Send(new GetMyAddressesQuery(), cancellationToken); return Ok(result); } [HttpGet("user/{userId}")] - [Authorize] + [Authorize(Roles = "Admin")] public async Task>> GetAddressesByUserId( string userId, [FromQuery] bool activeOnly = false, @@ -55,6 +44,6 @@ public class AddressesController(IMediator mediator) : ControllerBase CancellationToken cancellationToken = default) { var result = await mediator.Send(command, cancellationToken); - return CreatedAtAction(nameof(GetAddressById), new { id = result.Id }, result); + return CreatedAtAction(nameof(CreateAddress), new { id = result.Id }, result); } } \ No newline at end of file -- 2.49.1 From a8ea4b41ee411d4d428981669a7e2be52f6852f2 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:30:25 +0300 Subject: [PATCH 6/8] Holy moly...... --- ...eateAddressHandler.cs => CreateAddress.cs} | 2 +- ...erIdHandler.cs => GetAddressesByUserId.cs} | 2 +- ...yAddressesHandler.cs => GetMyAddresses.cs} | 2 +- ...teCategoryHandler.cs => CreateCategory.cs} | 2 +- ...teCategoryHandler.cs => DeleteCategory.cs} | 2 +- ...tCategoriesHandler.cs => GetCategories.cs} | 2 +- ...teCategoryHandler.cs => UpdateCategory.cs} | 2 +- .../{CreateOrderHandler.cs => CreateOrder.cs} | 5 +- ...GetOrderByIdHandler.cs => GetOrderById.cs} | 2 +- ...tIdHandler.cs => GetOrdersByMerchantId.cs} | 2 +- ...yUserIdHandler.cs => GetOrdersByUserId.cs} | 2 +- ...iantHandler.cs => CreateProductVariant.cs} | 2 +- ...iantHandler.cs => DeleteProductVariant.cs} | 2 +- ...riantsHandler.cs => GetProductVariants.cs} | 2 +- ...iantHandler.cs => UpdateProductVariant.cs} | 2 +- ...eateProductHandler.cs => CreateProduct.cs} | 2 +- ...leteProductHandler.cs => DeleteProduct.cs} | 2 +- .../Commands/Products/GetProductById.cs | 6 + .../{GetProductsHandler.cs => GetProducts.cs} | 2 +- ...dateProductCommand.cs => UpdateProduct.cs} | 6 +- ...teUserRoleHandler.cs => DeleteUserRole.cs} | 2 +- .../{GetAllRolesHandler.cs => GetAllRoles.cs} | 2 +- ...GetUserRolesHandler.cs => GetUserRoles.cs} | 2 +- ...rFullNameHandler.cs => SetUserFullName.cs} | 2 +- ...SetUserPhoneHandler.cs => SetUserPhone.cs} | 2 +- .../{SetUserRoleHandler.cs => SetUserRole.cs} | 2 +- .../Users/{SyncUserHandler.cs => SyncUser.cs} | 2 +- .../CreateAddressCommandValidator.cs | 117 ++++++++++++++++++ .../Orders/CreateOrderCommandValidator.cs | 76 ++++++++++++ .../Products/UpdateProductCommandValidator.cs | 2 +- .../Controllers/ProductsController.cs | 2 +- src/Imprink.WebApi/Startup.cs | 2 +- .../CreateCategoryHandlerIntegrationTest.cs | 8 +- .../UpdateCategoryHandlerUnitTest.cs | 8 +- 34 files changed, 238 insertions(+), 42 deletions(-) rename src/Imprink.Application/Commands/Addresses/{CreateAddressHandler.cs => CreateAddress.cs} (98%) rename src/Imprink.Application/Commands/Addresses/{GetAddressesByUserIdHandler.cs => GetAddressesByUserId.cs} (96%) rename src/Imprink.Application/Commands/Addresses/{GetMyAddressesHandler.cs => GetMyAddresses.cs} (95%) rename src/Imprink.Application/Commands/Categories/{CreateCategoryHandler.cs => CreateCategory.cs} (98%) rename src/Imprink.Application/Commands/Categories/{DeleteCategoryHandler.cs => DeleteCategory.cs} (96%) rename src/Imprink.Application/Commands/Categories/{GetCategoriesHandler.cs => GetCategories.cs} (97%) rename src/Imprink.Application/Commands/Categories/{UpdateCategoryHandler.cs => UpdateCategory.cs} (98%) rename src/Imprink.Application/Commands/Orders/{CreateOrderHandler.cs => CreateOrder.cs} (96%) rename src/Imprink.Application/Commands/Orders/{GetOrderByIdHandler.cs => GetOrderById.cs} (96%) rename src/Imprink.Application/Commands/Orders/{GetOrdersByMerchantIdHandler.cs => GetOrdersByMerchantId.cs} (96%) rename src/Imprink.Application/Commands/Orders/{GetOrdersByUserIdHandler.cs => GetOrdersByUserId.cs} (96%) rename src/Imprink.Application/Commands/ProductVariants/{CreateProductVariantHandler.cs => CreateProductVariant.cs} (97%) rename src/Imprink.Application/Commands/ProductVariants/{DeleteProductVariantHandler.cs => DeleteProductVariant.cs} (96%) rename src/Imprink.Application/Commands/ProductVariants/{GetProductVariantsHandler.cs => GetProductVariants.cs} (97%) rename src/Imprink.Application/Commands/ProductVariants/{UpdateProductVariantHandler.cs => UpdateProductVariant.cs} (97%) rename src/Imprink.Application/Commands/Products/{CreateProductHandler.cs => CreateProduct.cs} (97%) rename src/Imprink.Application/Commands/Products/{DeleteProductHandler.cs => DeleteProduct.cs} (95%) create mode 100644 src/Imprink.Application/Commands/Products/GetProductById.cs rename src/Imprink.Application/Commands/Products/{GetProductsHandler.cs => GetProducts.cs} (98%) rename src/Imprink.Application/Commands/Products/{UpdateProductCommand.cs => UpdateProduct.cs} (95%) rename src/Imprink.Application/Commands/Users/{DeleteUserRoleHandler.cs => DeleteUserRole.cs} (97%) rename src/Imprink.Application/Commands/Users/{GetAllRolesHandler.cs => GetAllRoles.cs} (94%) rename src/Imprink.Application/Commands/Users/{GetUserRolesHandler.cs => GetUserRoles.cs} (96%) rename src/Imprink.Application/Commands/Users/{SetUserFullNameHandler.cs => SetUserFullName.cs} (97%) rename src/Imprink.Application/Commands/Users/{SetUserPhoneHandler.cs => SetUserPhone.cs} (97%) rename src/Imprink.Application/Commands/Users/{SetUserRoleHandler.cs => SetUserRole.cs} (97%) rename src/Imprink.Application/Commands/Users/{SyncUserHandler.cs => SyncUser.cs} (97%) create mode 100644 src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs create mode 100644 src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs diff --git a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs b/src/Imprink.Application/Commands/Addresses/CreateAddress.cs similarity index 98% rename from src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs rename to src/Imprink.Application/Commands/Addresses/CreateAddress.cs index 9e9a94c..96b8be8 100644 --- a/src/Imprink.Application/Commands/Addresses/CreateAddressHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/CreateAddress.cs @@ -27,7 +27,7 @@ public class CreateAddressCommand : IRequest public bool IsActive { get; set; } = true; } -public class CreateAddressHandler( +public class CreateAddress( IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) diff --git a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs similarity index 96% rename from src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs rename to src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs index 5816518..ab5a66e 100644 --- a/src/Imprink.Application/Commands/Addresses/GetAddressesByUserIdHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/GetAddressesByUserId.cs @@ -12,7 +12,7 @@ public class GetAddressesByUserIdQuery : IRequest> public string? AddressType { get; set; } } -public class GetAddressesByUserIdHandler( +public class GetAddressesByUserId( IUnitOfWork uw, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs b/src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs similarity index 95% rename from src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs rename to src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs index 3f7a565..1915d04 100644 --- a/src/Imprink.Application/Commands/Addresses/GetMyAddressesHandler.cs +++ b/src/Imprink.Application/Commands/Addresses/GetMyAddresses.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Addresses; public class GetMyAddressesQuery : IRequest>; -public class GetMyAddressesHandler( +public class GetMyAddresses( IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) diff --git a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/CreateCategory.cs similarity index 98% rename from src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs rename to src/Imprink.Application/Commands/Categories/CreateCategory.cs index d690cc4..44a95c1 100644 --- a/src/Imprink.Application/Commands/Categories/CreateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/CreateCategory.cs @@ -14,7 +14,7 @@ public class CreateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class CreateCategoryHandler( +public class CreateCategory( IUnitOfWork unitOfWork) : IRequestHandler { diff --git a/src/Imprink.Application/Commands/Categories/DeleteCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/DeleteCategory.cs similarity index 96% rename from src/Imprink.Application/Commands/Categories/DeleteCategoryHandler.cs rename to src/Imprink.Application/Commands/Categories/DeleteCategory.cs index 3becc84..172b46b 100644 --- a/src/Imprink.Application/Commands/Categories/DeleteCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/DeleteCategory.cs @@ -7,7 +7,7 @@ public class DeleteCategoryCommand : IRequest public Guid Id { get; init; } } -public class DeleteCategoryHandler( +public class DeleteCategory( IUnitOfWork unitOfWork) : IRequestHandler { diff --git a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs b/src/Imprink.Application/Commands/Categories/GetCategories.cs similarity index 97% rename from src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs rename to src/Imprink.Application/Commands/Categories/GetCategories.cs index 1e5fcbd..0a28126 100644 --- a/src/Imprink.Application/Commands/Categories/GetCategoriesHandler.cs +++ b/src/Imprink.Application/Commands/Categories/GetCategories.cs @@ -10,7 +10,7 @@ public class GetCategoriesQuery : IRequest> public bool RootCategoriesOnly { get; set; } = false; } -public class GetCategoriesHandler( +public class GetCategories( IUnitOfWork unitOfWork) : IRequestHandler> { diff --git a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs b/src/Imprink.Application/Commands/Categories/UpdateCategory.cs similarity index 98% rename from src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs rename to src/Imprink.Application/Commands/Categories/UpdateCategory.cs index 975d3c5..aa2db76 100644 --- a/src/Imprink.Application/Commands/Categories/UpdateCategoryHandler.cs +++ b/src/Imprink.Application/Commands/Categories/UpdateCategory.cs @@ -15,7 +15,7 @@ public class UpdateCategoryCommand : IRequest public Guid? ParentCategoryId { get; set; } } -public class UpdateCategoryHandler( +public class UpdateCategory( IUnitOfWork unitOfWork) : IRequestHandler { diff --git a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs b/src/Imprink.Application/Commands/Orders/CreateOrder.cs similarity index 96% rename from src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs rename to src/Imprink.Application/Commands/Orders/CreateOrder.cs index 3784708..024a32e 100644 --- a/src/Imprink.Application/Commands/Orders/CreateOrderHandler.cs +++ b/src/Imprink.Application/Commands/Orders/CreateOrder.cs @@ -12,17 +12,14 @@ public class CreateOrderCommand : IRequest public int Quantity { get; set; } public Guid ProductId { get; set; } public Guid ProductVariantId { get; set; } - public string? Notes { get; set; } - public string? MerchantId { get; set; } public string? ComposingImageUrl { get; set; } public string[]? OriginalImageUrls { get; set; } = []; public string? CustomizationImageUrl { get; set; } = null!; public string? CustomizationDescription { get; set; } = null!; - public Guid AddressId { get; set; } } -public class CreateOrderHandler( +public class CreateOrder( IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) diff --git a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrderById.cs similarity index 96% rename from src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs rename to src/Imprink.Application/Commands/Orders/GetOrderById.cs index f9d0770..8749cb3 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrderByIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrderById.cs @@ -11,7 +11,7 @@ public class GetOrderByIdQuery : IRequest public bool IncludeDetails { get; set; } } -public class GetOrderByIdHandler( +public class GetOrderById( IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs similarity index 96% rename from src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs rename to src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs index 93b30ac..10b8080 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByMerchantId.cs @@ -11,7 +11,7 @@ public class GetOrdersByMerchantIdQuery : IRequest> public bool IncludeDetails { get; set; } } -public class GetOrdersByMerchantIdHandler( +public class GetOrdersByMerchantId( IUnitOfWork uw, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs b/src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs similarity index 96% rename from src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs rename to src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs index 41dd354..7889877 100644 --- a/src/Imprink.Application/Commands/Orders/GetOrdersByUserIdHandler.cs +++ b/src/Imprink.Application/Commands/Orders/GetOrdersByUserId.cs @@ -11,7 +11,7 @@ public class GetOrdersByUserIdQuery : IRequest> public bool IncludeDetails { get; set; } } -public class GetOrdersByUserIdHandler( +public class GetOrdersByUserId( IUnitOfWork uw, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs similarity index 97% rename from src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs index 1444882..ba0db6f 100644 --- a/src/Imprink.Application/Commands/ProductVariants/CreateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/CreateProductVariant.cs @@ -17,7 +17,7 @@ public class CreateProductVariantCommand : IRequest public bool IsActive { get; set; } = true; } -public class CreateProductVariantHandler( +public class CreateProductVariant( IUnitOfWork unitOfWork, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs similarity index 96% rename from src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs index d461975..3fda9f1 100644 --- a/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/DeleteProductVariant.cs @@ -7,7 +7,7 @@ public class DeleteProductVariantCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductVariantHandler( +public class DeleteProductVariant( IUnitOfWork unitOfWork) : IRequestHandler { diff --git a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs b/src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs similarity index 97% rename from src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs index 1e56414..a7ed4ac 100644 --- a/src/Imprink.Application/Commands/ProductVariants/GetProductVariantsHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/GetProductVariants.cs @@ -12,7 +12,7 @@ public class GetProductVariantsQuery : IRequest> public bool InStockOnly { get; set; } = false; } -public class GetProductVariantsHandler( +public class GetProductVariants( IUnitOfWork unitOfWork, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs similarity index 97% rename from src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs rename to src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs index e45232c..b91ee4b 100644 --- a/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariantHandler.cs +++ b/src/Imprink.Application/Commands/ProductVariants/UpdateProductVariant.cs @@ -18,7 +18,7 @@ public class UpdateProductVariantCommand : IRequest public bool IsActive { get; set; } } -public class UpdateProductVariantHandler( +public class UpdateProductVariant( IUnitOfWork unitOfWork, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs b/src/Imprink.Application/Commands/Products/CreateProduct.cs similarity index 97% rename from src/Imprink.Application/Commands/Products/CreateProductHandler.cs rename to src/Imprink.Application/Commands/Products/CreateProduct.cs index 7b93c3c..9f10a4e 100644 --- a/src/Imprink.Application/Commands/Products/CreateProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/CreateProduct.cs @@ -16,7 +16,7 @@ public class CreateProductCommand : IRequest public Guid? CategoryId { get; set; } } -public class CreateProductHandler( +public class CreateProduct( IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs b/src/Imprink.Application/Commands/Products/DeleteProduct.cs similarity index 95% rename from src/Imprink.Application/Commands/Products/DeleteProductHandler.cs rename to src/Imprink.Application/Commands/Products/DeleteProduct.cs index c839fad..a032586 100644 --- a/src/Imprink.Application/Commands/Products/DeleteProductHandler.cs +++ b/src/Imprink.Application/Commands/Products/DeleteProduct.cs @@ -8,7 +8,7 @@ public class DeleteProductCommand : IRequest public Guid Id { get; set; } } -public class DeleteProductHandler( +public class DeleteProduct( IUnitOfWork uw) : IRequestHandler { diff --git a/src/Imprink.Application/Commands/Products/GetProductById.cs b/src/Imprink.Application/Commands/Products/GetProductById.cs new file mode 100644 index 0000000..565aaaf --- /dev/null +++ b/src/Imprink.Application/Commands/Products/GetProductById.cs @@ -0,0 +1,6 @@ +namespace Imprink.Application.Commands.Products; + +public class GetProductById +{ + +} \ No newline at end of file diff --git a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs b/src/Imprink.Application/Commands/Products/GetProducts.cs similarity index 98% rename from src/Imprink.Application/Commands/Products/GetProductsHandler.cs rename to src/Imprink.Application/Commands/Products/GetProducts.cs index 0ef998d..8b3c97d 100644 --- a/src/Imprink.Application/Commands/Products/GetProductsHandler.cs +++ b/src/Imprink.Application/Commands/Products/GetProducts.cs @@ -9,7 +9,7 @@ public class GetProductsQuery : IRequest> public ProductFilterParameters FilterParameters { get; set; } = new(); } -public class GetProductsHandler( +public class GetProducts( IUnitOfWork unitOfWork) : IRequestHandler> { diff --git a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs b/src/Imprink.Application/Commands/Products/UpdateProduct.cs similarity index 95% rename from src/Imprink.Application/Commands/Products/UpdateProductCommand.cs rename to src/Imprink.Application/Commands/Products/UpdateProduct.cs index c60cbb6..28defbe 100644 --- a/src/Imprink.Application/Commands/Products/UpdateProductCommand.cs +++ b/src/Imprink.Application/Commands/Products/UpdateProduct.cs @@ -4,7 +4,7 @@ using MediatR; namespace Imprink.Application.Commands.Products; -public class UpdateProductCommand : IRequest +public class UpdateProduct : IRequest { public Guid Id { get; set; } public string Name { get; set; } = null!; @@ -18,10 +18,10 @@ public class UpdateProductCommand : IRequest public class UpdateProductHandler( IUnitOfWork unitOfWork) - : IRequestHandler + : IRequestHandler { public async Task Handle( - UpdateProductCommand request, + UpdateProduct request, CancellationToken cancellationToken) { await unitOfWork.BeginTransactionAsync(cancellationToken); diff --git a/src/Imprink.Application/Commands/Users/DeleteUserRoleHandler.cs b/src/Imprink.Application/Commands/Users/DeleteUserRole.cs similarity index 97% rename from src/Imprink.Application/Commands/Users/DeleteUserRoleHandler.cs rename to src/Imprink.Application/Commands/Users/DeleteUserRole.cs index 6b55762..e648abb 100644 --- a/src/Imprink.Application/Commands/Users/DeleteUserRoleHandler.cs +++ b/src/Imprink.Application/Commands/Users/DeleteUserRole.cs @@ -7,7 +7,7 @@ namespace Imprink.Application.Commands.Users; public record DeleteUserRoleCommand(string Sub, Guid RoleId) : IRequest; -public class DeleteUserRoleHandler( +public class DeleteUserRole( IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetAllRoles.cs similarity index 94% rename from src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs rename to src/Imprink.Application/Commands/Users/GetAllRoles.cs index ec4d093..d3f8ba3 100644 --- a/src/Imprink.Application/Commands/Users/GetAllRolesHandler.cs +++ b/src/Imprink.Application/Commands/Users/GetAllRoles.cs @@ -6,7 +6,7 @@ namespace Imprink.Application.Commands.Users; public record GetAllRolesCommand : IRequest>; -public class GetAllRolesHandler( +public class GetAllRoles( IUnitOfWork uw, IMapper mapper): IRequestHandler> { diff --git a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs b/src/Imprink.Application/Commands/Users/GetUserRoles.cs similarity index 96% rename from src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs rename to src/Imprink.Application/Commands/Users/GetUserRoles.cs index 5517ee0..5e3bb1d 100644 --- a/src/Imprink.Application/Commands/Users/GetUserRolesHandler.cs +++ b/src/Imprink.Application/Commands/Users/GetUserRoles.cs @@ -7,7 +7,7 @@ namespace Imprink.Application.Commands.Users; public record GetUserRolesCommand(string Sub) : IRequest>; -public class GetUserRolesHandler( +public class GetUserRoles( IUnitOfWork uw, IMapper mapper) : IRequestHandler> diff --git a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs b/src/Imprink.Application/Commands/Users/SetUserFullName.cs similarity index 97% rename from src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserFullName.cs index 0f4a1ca..fd22823 100644 --- a/src/Imprink.Application/Commands/Users/SetUserFullNameHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserFullName.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Users; public record SetUserFullNameCommand(string FirstName, string LastName) : IRequest; -public class SetUserFullNameHandler( +public class SetUserFullName( IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) diff --git a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs b/src/Imprink.Application/Commands/Users/SetUserPhone.cs similarity index 97% rename from src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserPhone.cs index f3876e7..38ede88 100644 --- a/src/Imprink.Application/Commands/Users/SetUserPhoneHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserPhone.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Users; public record SetUserPhoneCommand(string PhoneNumber) : IRequest; -public class SetUserPhoneHandler( +public class SetUserPhone( IUnitOfWork uw, IMapper mapper, ICurrentUserService userService) diff --git a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs b/src/Imprink.Application/Commands/Users/SetUserRole.cs similarity index 97% rename from src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs rename to src/Imprink.Application/Commands/Users/SetUserRole.cs index e826982..63d54bb 100644 --- a/src/Imprink.Application/Commands/Users/SetUserRoleHandler.cs +++ b/src/Imprink.Application/Commands/Users/SetUserRole.cs @@ -8,7 +8,7 @@ namespace Imprink.Application.Commands.Users; public record SetUserRoleCommand(string Sub, Guid RoleId) : IRequest; -public class SetUserRoleHandler( +public class SetUserRole( IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs b/src/Imprink.Application/Commands/Users/SyncUser.cs similarity index 97% rename from src/Imprink.Application/Commands/Users/SyncUserHandler.cs rename to src/Imprink.Application/Commands/Users/SyncUser.cs index e30da35..7f22f21 100644 --- a/src/Imprink.Application/Commands/Users/SyncUserHandler.cs +++ b/src/Imprink.Application/Commands/Users/SyncUser.cs @@ -7,7 +7,7 @@ namespace Imprink.Application.Commands.Users; public record SyncUserCommand(Auth0User User) : IRequest; -public class SyncUserHandler( +public class SyncUser( IUnitOfWork uw, IMapper mapper) : IRequestHandler diff --git a/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs b/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs new file mode 100644 index 0000000..28f4db2 --- /dev/null +++ b/src/Imprink.Application/Validation/Addresses/CreateAddressCommandValidator.cs @@ -0,0 +1,117 @@ +using FluentValidation; +using Imprink.Application.Commands.Addresses; + +namespace Imprink.Application.Validation.Addresses; + +public class CreateAddressCommandValidator : AbstractValidator +{ + public CreateAddressCommandValidator() + { + RuleFor(x => x.AddressType) + .NotEmpty() + .WithMessage("Address type is required.") + .MaximumLength(50) + .WithMessage("Address type must not exceed 50 characters.") + .Must(BeValidAddressType) + .WithMessage("Address type must be one of: Home, Work, Billing, Shipping, Other."); + + RuleFor(x => x.FirstName) + .MaximumLength(100) + .WithMessage("First name must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .When(x => !string.IsNullOrEmpty(x.FirstName)) + .WithMessage("First name can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.LastName) + .MaximumLength(100) + .WithMessage("Last name must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .When(x => !string.IsNullOrEmpty(x.LastName)) + .WithMessage("Last name can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.Company) + .MaximumLength(200) + .WithMessage("Company name must not exceed 200 characters."); + + RuleFor(x => x.AddressLine1) + .NotEmpty() + .WithMessage("Address line 1 is required.") + .MaximumLength(255) + .WithMessage("Address line 1 must not exceed 255 characters."); + + RuleFor(x => x.AddressLine2) + .MaximumLength(255) + .WithMessage("Address line 2 must not exceed 255 characters."); + + RuleFor(x => x.ApartmentNumber) + .MaximumLength(20) + .WithMessage("Apartment number must not exceed 20 characters."); + + RuleFor(x => x.BuildingNumber) + .MaximumLength(20) + .WithMessage("Building number must not exceed 20 characters."); + + RuleFor(x => x.Floor) + .MaximumLength(20) + .WithMessage("Floor must not exceed 20 characters."); + + RuleFor(x => x.City) + .NotEmpty() + .WithMessage("City is required.") + .MaximumLength(100) + .WithMessage("City must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .WithMessage("City can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.State) + .NotEmpty() + .WithMessage("State is required.") + .MaximumLength(100) + .WithMessage("State must not exceed 100 characters."); + + RuleFor(x => x.PostalCode) + .NotEmpty() + .WithMessage("Postal code is required.") + .MaximumLength(20) + .WithMessage("Postal code must not exceed 20 characters.") + .Matches(@"^[a-zA-Z0-9\s\-]*$") + .WithMessage("Postal code can only contain letters, numbers, spaces, and hyphens."); + + RuleFor(x => x.Country) + .NotEmpty() + .WithMessage("Country is required.") + .MaximumLength(100) + .WithMessage("Country must not exceed 100 characters.") + .Matches(@"^[a-zA-Z\s\-'\.]*$") + .WithMessage("Country can only contain letters, spaces, hyphens, apostrophes, and periods."); + + RuleFor(x => x.PhoneNumber) + .MaximumLength(20) + .WithMessage("Phone number must not exceed 20 characters.") + .Matches(@"^[\+]?[0-9\s\-\(\)\.]*$") + .When(x => !string.IsNullOrEmpty(x.PhoneNumber)) + .WithMessage("Phone number format is invalid. Use numbers, spaces, hyphens, parentheses, periods, and optional + prefix."); + + RuleFor(x => x.Instructions) + .MaximumLength(500) + .WithMessage("Instructions must not exceed 500 characters."); + + RuleFor(x => x) + .Must(HaveNameOrCompany) + .WithMessage("Either first name and last name, or company name must be provided.") + .WithName("Address"); + } + + private static bool BeValidAddressType(string addressType) + { + var validTypes = new[] { "Home", "Work", "Billing", "Shipping", "Other" }; + return validTypes.Contains(addressType, StringComparer.OrdinalIgnoreCase); + } + + private static bool HaveNameOrCompany(CreateAddressCommand command) + { + var hasName = !string.IsNullOrWhiteSpace(command.FirstName) && !string.IsNullOrWhiteSpace(command.LastName); + var hasCompany = !string.IsNullOrWhiteSpace(command.Company); + return hasName || hasCompany; + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs new file mode 100644 index 0000000..5f30658 --- /dev/null +++ b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs @@ -0,0 +1,76 @@ +using FluentValidation; +using Imprink.Application.Commands.Orders; + +namespace Imprink.Application.Validation.Orders; + +public class CreateOrderCommandValidator : AbstractValidator +{ + public CreateOrderCommandValidator() + { + RuleFor(x => x.Quantity) + .GreaterThan(0) + .WithMessage("Quantity must be greater than 0.") + .LessThanOrEqualTo(1000) + .WithMessage("Quantity cannot exceed 1000 items per order."); + + RuleFor(x => x.ProductId) + .NotEmpty() + .WithMessage("Product ID is required."); + + RuleFor(x => x.ProductVariantId) + .NotEmpty() + .WithMessage("Product variant ID is required."); + + RuleFor(x => x.AddressId) + .NotEmpty() + .WithMessage("Address ID is required."); + + RuleFor(x => x.ComposingImageUrl) + .MaximumLength(2048) + .WithMessage("Composing image URL must not exceed 2048 characters.") + .Must(BeValidUrl) + .When(x => !string.IsNullOrEmpty(x.ComposingImageUrl)) + .WithMessage("Composing image URL must be a valid URL."); + + RuleFor(x => x.CustomizationImageUrl) + .MaximumLength(2048) + .WithMessage("Customization image URL must not exceed 2048 characters.") + .Must(BeValidUrl) + .When(x => !string.IsNullOrEmpty(x.CustomizationImageUrl)) + .WithMessage("Customization image URL must be a valid URL."); + + RuleFor(x => x.CustomizationDescription) + .MaximumLength(2000) + .WithMessage("Customization description must not exceed 2000 characters."); + + RuleFor(x => x.OriginalImageUrls) + .Must(HaveValidImageUrls) + .When(x => x.OriginalImageUrls != null && x.OriginalImageUrls.Length > 0) + .WithMessage("All original image URLs must be valid URLs.") + .Must(x => x == null || x.Length <= 10) + .WithMessage("Cannot have more than 10 original images."); + + RuleForEach(x => x.OriginalImageUrls) + .MaximumLength(2048) + .WithMessage("Each original image URL must not exceed 2048 characters.") + .Must(BeValidUrl) + .WithMessage("Each original image URL must be a valid URL."); + } + + private static bool BeValidUrl(string url) + { + if (string.IsNullOrEmpty(url)) + return true; + + return Uri.TryCreate(url, UriKind.Absolute, out var result) + && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps); + } + + private static bool HaveValidImageUrls(string[]? urls) + { + if (urls == null || urls.Length == 0) + return true; + + return urls.All(url => !string.IsNullOrEmpty(url) && BeValidUrl(url)); + } +} \ No newline at end of file diff --git a/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs b/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs index 3607612..21ac02d 100644 --- a/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs +++ b/src/Imprink.Application/Validation/Products/UpdateProductCommandValidator.cs @@ -3,7 +3,7 @@ using Imprink.Application.Commands.Products; namespace Imprink.Application.Validation.Products; -public class UpdateProductCommandValidator : AbstractValidator +public class UpdateProductCommandValidator : AbstractValidator { public UpdateProductCommandValidator() { diff --git a/src/Imprink.WebApi/Controllers/ProductsController.cs b/src/Imprink.WebApi/Controllers/ProductsController.cs index 9c08e74..6d7c0b2 100644 --- a/src/Imprink.WebApi/Controllers/ProductsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductsController.cs @@ -34,7 +34,7 @@ public class ProductsController(IMediator mediator) : ControllerBase [Authorize(Roles = "Admin")] public async Task> UpdateProduct( Guid id, - [FromBody] UpdateProductCommand command) + [FromBody] UpdateProduct command) { if (id != command.Id) return BadRequest("ID mismatch"); diff --git a/src/Imprink.WebApi/Startup.cs b/src/Imprink.WebApi/Startup.cs index 564050c..06c954b 100644 --- a/src/Imprink.WebApi/Startup.cs +++ b/src/Imprink.WebApi/Startup.cs @@ -24,7 +24,7 @@ public static class Startup services.AddDatabaseContexts(builder.Configuration); - services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateProductHandler).Assembly)); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(CreateProduct).Assembly)); services.AddValidatorsFromAssembly(typeof(Auth0UserValidator).Assembly); diff --git a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs index 67430c4..3f95438 100644 --- a/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs +++ b/tests/Imprink.Application.Tests/CreateCategoryHandlerIntegrationTest.cs @@ -15,7 +15,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable { private readonly ApplicationDbContext _context; private readonly IServiceProvider _serviceProvider; - private readonly CreateCategoryHandler _handler; + private readonly CreateCategory _handler; private readonly SqliteConnection _connection; public CreateCategoryHandlerIntegrationTest() @@ -38,11 +38,11 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); _serviceProvider = services.BuildServiceProvider(); _context = _serviceProvider.GetRequiredService(); - _handler = _serviceProvider.GetRequiredService(); + _handler = _serviceProvider.GetRequiredService(); _context.Database.EnsureCreated(); } @@ -216,7 +216,7 @@ public class CreateCategoryHandlerIntegrationTest : IDisposable var initialCount = await _context.Categories.CountAsync(); - var handler = _serviceProvider.GetRequiredService(); + var handler = _serviceProvider.GetRequiredService(); var result = await handler.Handle(command, CancellationToken.None); diff --git a/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs b/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs index 3336fe8..6fd74b8 100644 --- a/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs +++ b/tests/Imprink.Application.Tests/UpdateCategoryHandlerUnitTest.cs @@ -6,13 +6,13 @@ using Moq; namespace Imprink.Application.Tests; -public class UpdateCategoryHandlerTests +public class UpdateCategoryTests { private readonly Mock _unitOfWorkMock; private readonly Mock _categoryRepositoryMock; - private readonly UpdateCategoryHandler _handler; + private readonly UpdateCategory _handler; - public UpdateCategoryHandlerTests() + public UpdateCategoryTests() { _unitOfWorkMock = new Mock(); _categoryRepositoryMock = new Mock(); @@ -20,7 +20,7 @@ public class UpdateCategoryHandlerTests _unitOfWorkMock.Setup(x => x.CategoryRepository) .Returns(_categoryRepositoryMock.Object); - _handler = new UpdateCategoryHandler(_unitOfWorkMock.Object); + _handler = new UpdateCategory(_unitOfWorkMock.Object); } [Fact] -- 2.49.1 From c7dcbc1926df5ce9bc50b7be634b425dc58b3b4e Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 01:36:12 +0300 Subject: [PATCH 7/8] Add product by id handler --- .../Commands/Products/GetProductById.cs | 49 ++++++++++++++++++- ...> 20250625223159_InitialSetup.Designer.cs} | 2 +- ...etup.cs => 20250625223159_InitialSetup.cs} | 0 .../Controllers/ProductsController.cs | 12 +++++ 4 files changed, 60 insertions(+), 3 deletions(-) rename src/Imprink.Infrastructure/Migrations/{20250625211612_InitialSetup.Designer.cs => 20250625223159_InitialSetup.Designer.cs} (99%) rename src/Imprink.Infrastructure/Migrations/{20250625211612_InitialSetup.cs => 20250625223159_InitialSetup.cs} (100%) diff --git a/src/Imprink.Application/Commands/Products/GetProductById.cs b/src/Imprink.Application/Commands/Products/GetProductById.cs index 565aaaf..44f2d7a 100644 --- a/src/Imprink.Application/Commands/Products/GetProductById.cs +++ b/src/Imprink.Application/Commands/Products/GetProductById.cs @@ -1,6 +1,51 @@ +using Imprink.Application.Dtos; +using MediatR; + namespace Imprink.Application.Commands.Products; -public class GetProductById +public class GetProductByIdQuery : IRequest { - + public Guid ProductId { get; set; } +} + +public class GetProductById( + IUnitOfWork unitOfWork) + : IRequestHandler +{ + public async Task Handle( + GetProductByIdQuery request, + CancellationToken cancellationToken) + { + var product = await unitOfWork.ProductRepository + .GetByIdAsync(request.ProductId, cancellationToken); + + if (product == null) + return null; + + return new ProductDto + { + Id = product.Id, + Name = product.Name, + Description = product.Description, + BasePrice = product.BasePrice, + IsCustomizable = product.IsCustomizable, + IsActive = product.IsActive, + ImageUrl = product.ImageUrl, + CategoryId = product.CategoryId, + Category = new CategoryDto + { + Id = product.Category.Id, + Name = product.Category.Name, + Description = product.Category.Description, + ImageUrl = product.Category.ImageUrl, + SortOrder = product.Category.SortOrder, + IsActive = product.Category.IsActive, + ParentCategoryId = product.Category.ParentCategoryId, + CreatedAt = product.Category.CreatedAt, + ModifiedAt = product.Category.ModifiedAt + }, + CreatedAt = product.CreatedAt, + ModifiedAt = product.ModifiedAt + }; + } } \ No newline at end of file diff --git a/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs similarity index 99% rename from src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs rename to src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs index 65b0a44..8e73eb7 100644 --- a/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.Designer.cs +++ b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Imprink.Infrastructure.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20250625211612_InitialSetup")] + [Migration("20250625223159_InitialSetup")] partial class InitialSetup { /// diff --git a/src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.cs b/src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs similarity index 100% rename from src/Imprink.Infrastructure/Migrations/20250625211612_InitialSetup.cs rename to src/Imprink.Infrastructure/Migrations/20250625223159_InitialSetup.cs diff --git a/src/Imprink.WebApi/Controllers/ProductsController.cs b/src/Imprink.WebApi/Controllers/ProductsController.cs index 6d7c0b2..9a4fef9 100644 --- a/src/Imprink.WebApi/Controllers/ProductsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductsController.cs @@ -21,6 +21,18 @@ public class ProductsController(IMediator mediator) : ControllerBase return Ok(result); } + [HttpGet("{id:guid}")] + [AllowAnonymous] + public async Task>> GetProductById( + Guid id, + CancellationToken cancellationToken) + { + var result = await mediator + .Send(new GetProductByIdQuery { ProductId = id}, cancellationToken); + + return Ok(result); + } + [HttpPost] [Authorize(Roles = "Admin")] public async Task>> CreateProduct( -- 2.49.1 From c137a03a0ad7ffd9bfa0f63d325270f271a040aa Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Thu, 26 Jun 2025 02:51:14 +0300 Subject: [PATCH 8/8] Orders work... --- .../Commands/Orders/CreateOrder.cs | 1 - .../Commands/Products/GetProductById.cs | 2 +- .../Orders/CreateOrderCommandValidator.cs | 7 - .../Controllers/ProductVariantsController.cs | 5 +- .../Controllers/ProductsController.cs | 2 +- ui/src/app/builder/[id]/page.tsx | 800 ++++++++++++++++++ 6 files changed, 805 insertions(+), 12 deletions(-) create mode 100644 ui/src/app/builder/[id]/page.tsx diff --git a/src/Imprink.Application/Commands/Orders/CreateOrder.cs b/src/Imprink.Application/Commands/Orders/CreateOrder.cs index 024a32e..098e5c8 100644 --- a/src/Imprink.Application/Commands/Orders/CreateOrder.cs +++ b/src/Imprink.Application/Commands/Orders/CreateOrder.cs @@ -12,7 +12,6 @@ public class CreateOrderCommand : IRequest public int Quantity { get; set; } public Guid ProductId { get; set; } public Guid ProductVariantId { get; set; } - public string? ComposingImageUrl { get; set; } public string[]? OriginalImageUrls { get; set; } = []; public string? CustomizationImageUrl { get; set; } = null!; public string? CustomizationDescription { get; set; } = null!; diff --git a/src/Imprink.Application/Commands/Products/GetProductById.cs b/src/Imprink.Application/Commands/Products/GetProductById.cs index 44f2d7a..d8b5bb3 100644 --- a/src/Imprink.Application/Commands/Products/GetProductById.cs +++ b/src/Imprink.Application/Commands/Products/GetProductById.cs @@ -17,7 +17,7 @@ public class GetProductById( CancellationToken cancellationToken) { var product = await unitOfWork.ProductRepository - .GetByIdAsync(request.ProductId, cancellationToken); + .GetByIdWithCategoryAsync(request.ProductId, cancellationToken); if (product == null) return null; diff --git a/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs index 5f30658..71ec02e 100644 --- a/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs +++ b/src/Imprink.Application/Validation/Orders/CreateOrderCommandValidator.cs @@ -25,13 +25,6 @@ public class CreateOrderCommandValidator : AbstractValidator .NotEmpty() .WithMessage("Address ID is required."); - RuleFor(x => x.ComposingImageUrl) - .MaximumLength(2048) - .WithMessage("Composing image URL must not exceed 2048 characters.") - .Must(BeValidUrl) - .When(x => !string.IsNullOrEmpty(x.ComposingImageUrl)) - .WithMessage("Composing image URL must be a valid URL."); - RuleFor(x => x.CustomizationImageUrl) .MaximumLength(2048) .WithMessage("Customization image URL must not exceed 2048 characters.") diff --git a/src/Imprink.WebApi/Controllers/ProductVariantsController.cs b/src/Imprink.WebApi/Controllers/ProductVariantsController.cs index a1c8636..fa9c198 100644 --- a/src/Imprink.WebApi/Controllers/ProductVariantsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductVariantsController.cs @@ -10,11 +10,12 @@ namespace Imprink.WebApi.Controllers; [Route("/api/products/variants")] public class ProductVariantsController(IMediator mediator) : ControllerBase { - [HttpGet] + [HttpGet("{id:guid}")] [AllowAnonymous] public async Task>> GetProductVariants( - [FromQuery] GetProductVariantsQuery query) + Guid id) { + var query = new GetProductVariantsQuery { ProductId = id }; return Ok(await mediator.Send(query)); } diff --git a/src/Imprink.WebApi/Controllers/ProductsController.cs b/src/Imprink.WebApi/Controllers/ProductsController.cs index 9a4fef9..6d23d6c 100644 --- a/src/Imprink.WebApi/Controllers/ProductsController.cs +++ b/src/Imprink.WebApi/Controllers/ProductsController.cs @@ -23,7 +23,7 @@ public class ProductsController(IMediator mediator) : ControllerBase [HttpGet("{id:guid}")] [AllowAnonymous] - public async Task>> GetProductById( + public async Task> GetProductById( Guid id, CancellationToken cancellationToken) { diff --git a/ui/src/app/builder/[id]/page.tsx b/ui/src/app/builder/[id]/page.tsx new file mode 100644 index 0000000..2268fff --- /dev/null +++ b/ui/src/app/builder/[id]/page.tsx @@ -0,0 +1,800 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import clientApi from '@/lib/clientApi'; +import { + Box, + Button, + Card, + CardContent, + CardMedia, + Typography, + Stepper, + Step, + StepLabel, + Grid, + TextField, + IconButton, + Radio, + RadioGroup, + FormControlLabel, + FormControl, + FormLabel, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Chip, + Container, + Paper, + Fade, + CircularProgress +} from '@mui/material'; +import { + Add as AddIcon, + Remove as RemoveIcon, + LocationOn as LocationIcon, + AddLocation as AddLocationIcon +} from '@mui/icons-material'; + +interface Category { + id: string; + name: string; + description: string; + imageUrl: string; + sortOrder: number; + isActive: boolean; + parentCategoryId: string; + createdAt: string; + modifiedAt: string; +} + +interface Product { + id: string; + name: string; + description: string; + basePrice: number; + isCustomizable: boolean; + isActive: boolean; + imageUrl: string; + categoryId: string; + category: Category; + createdAt: string; + modifiedAt: string; +} + +interface Variant { + id: string; + productId: string; + size: string; + color: string; + price: number; + imageUrl: string; + sku: string; + stockQuantity: number; + isActive: boolean; + product: Product; + createdAt: string; + modifiedAt: string; +} + +interface Address { + id: string; + userId: string; + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +interface NewAddress { + addressType: string; + firstName: string; + lastName: string; + company: string; + addressLine1: string; + addressLine2: string; + apartmentNumber: string; + buildingNumber: string; + floor: string; + city: string; + state: string; + postalCode: string; + country: string; + phoneNumber: string; + instructions: string; + isDefault: boolean; + isActive: boolean; +} + +const steps = ['Product Details', 'Select Variant', 'Choose Quantity', 'Delivery Address', 'Review & Order']; + +export default function OrderBuilder() { + const router = useRouter(); + const params = useParams(); + const productId = params.id as string; + + const [activeStep, setActiveStep] = useState(0); + const [loading, setLoading] = useState(false); + const [product, setProduct] = useState(null); + const [variants, setVariants] = useState([]); + const [addresses, setAddresses] = useState([]); + const [selectedVariant, setSelectedVariant] = useState(null); + const [quantity, setQuantity] = useState(1); + const [selectedAddress, setSelectedAddress] = useState
(null); + const [showAddressDialog, setShowAddressDialog] = useState(false); + const [newAddress, setNewAddress] = useState({ + addressType: 'Home', + firstName: '', + lastName: '', + company: '', + addressLine1: '', + addressLine2: '', + apartmentNumber: '', + buildingNumber: '', + floor: '', + city: '', + state: '', + postalCode: '', + country: '', + phoneNumber: '', + instructions: '', + isDefault: false, + isActive: true + }); + + useEffect(() => { + if (productId) { + loadProduct(); + } + }, [productId]); + + const loadProduct = async () => { + setLoading(true); + try { + const productData = await clientApi.get(`/products/${productId}`); + setProduct(productData.data); + } catch (error) { + console.error('Failed to load product:', error); + } finally { + setLoading(false); + } + }; + + const loadVariants = async () => { + setLoading(true); + try { + const variantsData = await clientApi.get(`/products/variants/${productId}`); + setVariants(variantsData.data); + } catch (error) { + console.error('Failed to load variants:', error); + } finally { + setLoading(false); + } + }; + + const loadAddresses = async () => { + setLoading(true); + try { + const addressesData = await clientApi.get('/addresses/me'); + setAddresses(addressesData.data); + if (addressesData.data.length > 0) { + const defaultAddress = addressesData.data.find((addr: Address) => addr.isDefault) || addressesData.data[0]; + setSelectedAddress(defaultAddress); + } + } catch (error) { + console.error('Failed to load addresses:', error); + } finally { + setLoading(false); + } + }; + + const handleNext = () => { + if (activeStep === 0 && product) { + loadVariants(); + } else if (activeStep === 2) { + loadAddresses(); + } + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleQuantityChange = (delta: number) => { + const newQuantity = quantity + delta; + if (newQuantity >= 1) { + setQuantity(newQuantity); + } + }; + + const handleAddAddress = async () => { + try { + const addedAddress = await clientApi.post('/addresses', newAddress); + setAddresses([...addresses, addedAddress.data]); + setSelectedAddress(addedAddress.data); + setShowAddressDialog(false); + setNewAddress({ + addressType: 'shipping', + firstName: '', + lastName: '', + company: '', + addressLine1: '', + addressLine2: '', + apartmentNumber: '', + buildingNumber: '', + floor: '', + city: '', + state: '', + postalCode: '', + country: '', + phoneNumber: '', + instructions: '', + isDefault: false, + isActive: true + }); + } catch (error) { + console.error('Failed to add address:', error); + } + }; + + const handlePlaceOrder = async () => { + if (!selectedVariant || !selectedAddress) return; + + const orderData = { + productId: product!.id, + productVariantId: selectedVariant.id, + quantity: quantity, + addressId: selectedAddress.id, + totalPrice: selectedVariant.price * quantity + }; + + try { + setLoading(true); + await clientApi.post('/orders', orderData); + router.push('/orders/success'); + } catch (error) { + console.error('Failed to place order:', error); + } finally { + setLoading(false); + } + }; + + const getTotalPrice = () => { + if (!selectedVariant) return 0; + return selectedVariant.price * quantity; + }; + + const canProceed = () => { + switch (activeStep) { + case 0: return product !== null; + case 1: return selectedVariant !== null; + case 2: return quantity > 0; + case 3: return selectedAddress !== null; + default: return true; + } + }; + + const renderStepContent = () => { + switch (activeStep) { + case 0: + return ( + + + {product && ( + + + + + {product.name} + + + {product.description} + + + ${product.basePrice.toFixed(2)} + + + + {product.isCustomizable && } + + + + )} + + + ); + + case 1: + return ( + + + + Select Variant + + + {variants.map((variant) => ( + + setSelectedVariant(variant)} + > + + + + {variant.size} - {variant.color} + + + SKU: {variant.sku} + + + ${variant.price.toFixed(2)} + + 0 ? 'success.main' : 'error.main'}> + {variant.stockQuantity > 0 ? `${variant.stockQuantity} in stock` : 'Out of stock'} + + + + + ))} + + + + ); + + case 2: + return ( + + + + Choose Quantity + + {selectedVariant && ( + + + + + + {selectedVariant.size} + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + ${selectedVariant.price.toFixed(2)} each + + + + + + + handleQuantityChange(-1)} + disabled={quantity <= 1} + > + + + { + const val = parseInt(e.target.value) || 1; + if (val >= 1) setQuantity(val); + }} + inputProps={{ + style: { textAlign: 'center', fontSize: '1.2rem' }, + min: 1 + }} + sx={{ width: 80 }} + /> + handleQuantityChange(1)}> + + + + + + + + Total: ${getTotalPrice().toFixed(2)} + + + + + )} + + + ); + + case 3: + return ( + + + + Select Delivery Address + + + { + const addr = addresses.find(a => a.id === e.target.value); + setSelectedAddress(addr || null); + }} + > + + {addresses.map((address) => ( + + + + } + label="" + sx={{ position: 'absolute', top: 8, right: 8 }} + /> + + + + {address.firstName} {address.lastName} + + {address.isDefault && } + + {address.company && ( + + {address.company} + + )} + + {address.addressLine1} + + {address.addressLine2 && ( + + {address.addressLine2} + + )} + + {address.city}, {address.state} {address.postalCode} + + + {address.country} + + {address.phoneNumber && ( + + Phone: {address.phoneNumber} + + )} + + + + ))} + + setShowAddressDialog(true)} + > + + + + + Add New Address + + + + + + + + + + + ); + + case 4: + return ( + + + + Review Your Order + + + + + + + Order Summary + + {selectedVariant && ( + + {selectedVariant.size} + + + {selectedVariant.product.name} + + + {selectedVariant.size} - {selectedVariant.color} + + + Quantity: {quantity} + + + + ${getTotalPrice().toFixed(2)} + + + )} + + + + + + + + Delivery Address + + {selectedAddress && ( + + + {selectedAddress.firstName} {selectedAddress.lastName} + + {selectedAddress.company && ( + + {selectedAddress.company} + + )} + + {selectedAddress.addressLine1} + + {selectedAddress.addressLine2 && ( + + {selectedAddress.addressLine2} + + )} + + {selectedAddress.city}, {selectedAddress.state} {selectedAddress.postalCode} + + + {selectedAddress.country} + + + )} + + + + + + + ); + + default: + return null; + } + }; + + if (loading && !product) { + return ( + + + + ); + } + + return ( + + + + {steps.map((label) => ( + + {label} + + ))} + + + + {renderStepContent()} + + + + + + + + + setShowAddressDialog(false)} maxWidth="md" fullWidth> + Add New Address + + + + setNewAddress({...newAddress, firstName: e.target.value})} + /> + + + setNewAddress({...newAddress, lastName: e.target.value})} + /> + + + setNewAddress({...newAddress, company: e.target.value})} + /> + + + setNewAddress({...newAddress, addressLine1: e.target.value})} + /> + + + setNewAddress({...newAddress, addressLine2: e.target.value})} + /> + + + setNewAddress({...newAddress, apartmentNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, buildingNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, floor: e.target.value})} + /> + + + setNewAddress({...newAddress, city: e.target.value})} + /> + + + setNewAddress({...newAddress, state: e.target.value})} + /> + + + setNewAddress({...newAddress, postalCode: e.target.value})} + /> + + + setNewAddress({...newAddress, country: e.target.value})} + /> + + + setNewAddress({...newAddress, phoneNumber: e.target.value})} + /> + + + setNewAddress({...newAddress, instructions: e.target.value})} + /> + + + + + + + + + + ); +} \ No newline at end of file -- 2.49.1