From 6ca35db70de6938b2095e54465da49cf395ad636 Mon Sep 17 00:00:00 2001 From: "Md. Abul Kalam" Date: Mon, 2 Dec 2024 00:18:35 +0600 Subject: [PATCH 1/4] Issue #56: Update methods to integrate stripe --- .../Common/Mapping/CartMappingConfig.cs | 3 +- .../Common/Mapping/OrderMappingConfig.cs | 6 +- .../Common/Mapping/paymentMappingConfig.cs | 10 +- src/Shopizy.Api/Controllers/CartController.cs | 2 +- .../Controllers/PaymentController.cs | 18 +- src/Shopizy.Api/Controllers/UserController.cs | 2 +- .../Carts/Queries/GetCart/GetCartQuery.cs | 2 +- .../Interfaces/Services/IPaymentService.cs | 8 +- .../Common/models/CreateSaleRequest.cs | 25 + .../Common/models/CreateSaleResponse.cs | 22 + .../Common/models/CustomerResource.cs | 3 + .../Common/models/Metadata.cs | 7 + .../Common/models/PaymentResource.cs | 14 - .../CardNotPresentSaleCommand.cs} | 16 +- .../CardNotPresentSaleCommandHandler.cs | 119 +++ .../CreatePaymentSessionCommandHandler.cs | 67 -- .../UpdatePasswordCommandHadler.cs | 7 +- .../Order/CreateOrderRequest.cs | 2 +- .../Payment/CardNotPresentSaleRequest.cs | 12 + .../Payment/CreatePaymentSessionRequest.cs | 8 - .../Payment/PaymentSessionResponse.cs | 3 - .../CustomErrors/CustomErrors.Payment.cs | 5 + .../Common/CustomErrors/CustomErrors.User.cs | 5 + src/Shopizy.Domain/Orders/Order.cs | 6 + src/Shopizy.Domain/Payments/Payment.cs | 9 +- src/Shopizy.Domain/Users/User.cs | 7 + .../DependencyInjectionRegister.cs | 2 +- .../PaymentGateway/Stripe/StripeService.cs | 102 +-- ...71255_AddCustomerIdInUserTable.Designer.cs | 753 ++++++++++++++++++ ...20241201171255_AddCustomerIdInUserTable.cs | 29 + .../Migrations/AppDbContextModelSnapshot.cs | 6 +- .../Users/Persistence/UserConfigurations.cs | 2 + .../UpdateAddressCommandHadler.test.cs | 242 ++---- .../UpdatePasswordCommandHadler.test.cs | 99 ++- .../TestUtils/UpdatePasswordCommandUtils.cs | 18 + 35 files changed, 1254 insertions(+), 387 deletions(-) create mode 100644 src/Shopizy.Application/Common/models/CreateSaleRequest.cs create mode 100644 src/Shopizy.Application/Common/models/CreateSaleResponse.cs create mode 100644 src/Shopizy.Application/Common/models/CustomerResource.cs create mode 100644 src/Shopizy.Application/Common/models/Metadata.cs delete mode 100644 src/Shopizy.Application/Common/models/PaymentResource.cs rename src/Shopizy.Application/Payments/Commands/{CreatePaymentSession/CreatePaymentSessionCommand.cs => CardNotPresentSale/CardNotPresentSaleCommand.cs} (51%) create mode 100644 src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs delete mode 100644 src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommandHandler.cs create mode 100644 src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs delete mode 100644 src/Shopizy.Contracts/Payment/CreatePaymentSessionRequest.cs delete mode 100644 src/Shopizy.Contracts/Payment/PaymentSessionResponse.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.cs diff --git a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs index 4832de6..d81c71b 100644 --- a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs @@ -69,6 +69,7 @@ public void Register(TypeAdapterConfig config) src.Product.ProductImages == null ? null : src.Product.ProductImages.Select(pi => pi.ImageUrl) - ); + ) + .Map(dest => dest.Product.Price, src => src.Product.UnitPrice.Amount); } } diff --git a/src/Shopizy.Api/Common/Mapping/OrderMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/OrderMappingConfig.cs index 932e1e0..bb65445 100644 --- a/src/Shopizy.Api/Common/Mapping/OrderMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/OrderMappingConfig.cs @@ -24,7 +24,11 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.DeliveryChargeAmount, src => src.request.DeliveryCharge.Amount) .Map( dest => dest.DeliveryChargeCurrency, - src => (Currency)src.request.DeliveryCharge.Currency + src => + src.request.DeliveryCharge.Currency == "usd" ? Currency.usd + : src.request.DeliveryCharge.Currency == "bdt" ? Currency.bdt + : src.request.DeliveryCharge.Currency == "euro" ? Currency.euro + : Currency.usd ); config .NewConfig<(Guid UserId, Guid OrderId), GetOrderQuery>() diff --git a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs index defe9c3..5348911 100644 --- a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs @@ -1,7 +1,7 @@ using Ardalis.GuardClauses; using Mapster; using Shopizy.Application.Common.models; -using Shopizy.Application.Payments.Commands.CreatePaymentSession; +using Shopizy.Application.Payments.Commands.CardNotPresentSale; using Shopizy.Contracts.Payment; namespace Shopizy.Api.Common.Mapping; @@ -14,14 +14,10 @@ public void Register(TypeAdapterConfig config) config .NewConfig< - (Guid UserId, CreatePaymentSessionRequest request), - CreatePaymentSessionCommand + (Guid UserId, CardNotPresentSaleRequest request), + CardNotPresentSaleCommand >() .Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest, src => src.request); - - config.NewConfig(); - - config.NewConfig(); } } diff --git a/src/Shopizy.Api/Controllers/CartController.cs b/src/Shopizy.Api/Controllers/CartController.cs index 72e4b98..14dc1ab 100644 --- a/src/Shopizy.Api/Controllers/CartController.cs +++ b/src/Shopizy.Api/Controllers/CartController.cs @@ -87,7 +87,7 @@ UpdateProductQuantityRequest request ); } - [HttpDelete("{cartId:guid}/remove-product/{productId:guid}")] + [HttpDelete("{cartId:guid}/product/{productId:guid}")] [SwaggerResponse(StatusCodes.Status200OK, null, typeof(SuccessResult))] [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] diff --git a/src/Shopizy.Api/Controllers/PaymentController.cs b/src/Shopizy.Api/Controllers/PaymentController.cs index 2f95697..29d68e4 100644 --- a/src/Shopizy.Api/Controllers/PaymentController.cs +++ b/src/Shopizy.Api/Controllers/PaymentController.cs @@ -1,7 +1,7 @@ using MapsterMapper; using MediatR; using Microsoft.AspNetCore.Mvc; -using Shopizy.Application.Payments.Commands.CreatePaymentSession; +using Shopizy.Application.Payments.Commands.CardNotPresentSale; using Shopizy.Contracts.Common; using Shopizy.Contracts.Payment; using Swashbuckle.AspNetCore.Annotations; @@ -14,20 +14,20 @@ public class PaymentController(ISender mediator, IMapper mapper) : ApiController private readonly ISender _mediator = mediator; private readonly IMapper _mapper = mapper; - [HttpPost("session")] - [SwaggerResponse(StatusCodes.Status200OK, null, typeof(PaymentSessionResponse))] + [HttpPost] + [SwaggerResponse(StatusCodes.Status200OK, null, typeof(SuccessResult))] [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status409Conflict, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status500InternalServerError, null, typeof(ErrorResult))] - public async Task CreatePaymentSessoinAsync( - Guid userId, - CreatePaymentSessionRequest request - ) + public async Task CreateSaleAsync(Guid userId, CardNotPresentSaleRequest request) { - var command = _mapper.Map((userId, request)); + var command = _mapper.Map((userId, request)); var result = await _mediator.Send(command); - return result.Match(Payment => Ok(_mapper.Map(Payment)), Problem); + return result.Match( + success => Ok(SuccessResult.Success("Payment successfull collected.")), + Problem + ); } } diff --git a/src/Shopizy.Api/Controllers/UserController.cs b/src/Shopizy.Api/Controllers/UserController.cs index d856e09..12d6dc5 100644 --- a/src/Shopizy.Api/Controllers/UserController.cs +++ b/src/Shopizy.Api/Controllers/UserController.cs @@ -61,7 +61,7 @@ public async Task UpdatePasswordAsync(Guid userId, UpdatePassword var command = _mapper.Map((userId, request)); var result = await _mediator.Send(command); - return result.Match( + return result.Match( success => Ok(SuccessResult.Success("Successfully updated password.")), Problem ); diff --git a/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs b/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs index dbed11f..fc72d64 100644 --- a/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs +++ b/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs @@ -8,4 +8,4 @@ namespace Shopizy.Application.Carts.Queries.GetCart; [Authorize(Permissions = Permissions.Cart.Get, Policies = Policy.SelfOrAdmin)] -public record GetCartQuery(Guid UserId) : IRequest>; +public record GetCartQuery(Guid UserId) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Common/Interfaces/Services/IPaymentService.cs b/src/Shopizy.Application/Common/Interfaces/Services/IPaymentService.cs index d983c4d..af9cfec 100644 --- a/src/Shopizy.Application/Common/Interfaces/Services/IPaymentService.cs +++ b/src/Shopizy.Application/Common/Interfaces/Services/IPaymentService.cs @@ -10,11 +10,5 @@ Task> CreateCustomer( string name, CancellationToken cancellationToken ); - Task> CreateCheckoutSession( - string customerEmail, - decimal price, - string successUrl, - string cancelUrl, - CancellationToken cancellationToken = default - ); + Task> CreateSaleAsync(CreateSaleRequest request); } diff --git a/src/Shopizy.Application/Common/models/CreateSaleRequest.cs b/src/Shopizy.Application/Common/models/CreateSaleRequest.cs new file mode 100644 index 0000000..ded0621 --- /dev/null +++ b/src/Shopizy.Application/Common/models/CreateSaleRequest.cs @@ -0,0 +1,25 @@ +namespace Shopizy.Application.Common.models; + +public class CreateSaleRequest +{ + public string CustomerId { get; set; } + public string PaymentMethodId { get; set; } + public string Currency { get; set; } + public long Amount { get; set; } + public List? PaymentMethodTypes { get; set; } + public bool CapturePayment { get; set; } = true; + public string? SetupFutureUsage { get; set; } + public Dictionary? Metadata { get; set; } + + public void SetMetadata(Metadata metadata) + { + Metadata = new Dictionary { { "orderId", metadata.OrderId } }; + if (metadata.AdditionalData != null) + { + foreach (var item in metadata.AdditionalData) + { + Metadata[item.Key] = item.Value; + } + } + } +} diff --git a/src/Shopizy.Application/Common/models/CreateSaleResponse.cs b/src/Shopizy.Application/Common/models/CreateSaleResponse.cs new file mode 100644 index 0000000..120b87a --- /dev/null +++ b/src/Shopizy.Application/Common/models/CreateSaleResponse.cs @@ -0,0 +1,22 @@ +namespace Shopizy.Application.Common.models; + +public class CreateSaleResponse +{ + public int ResponseStatusCode { get; set; } + public string CustomerId { get; set; } + public long Amount { get; set; } + public string Currency { get; set; } + public string PaymentIntentId { get; set; } + public string ObjectType { get; set; } + public string CaptureMethod { get; set; } + + public string ChargeId { get; set; } + + public string PaymentMethodId { get; set; } + + public List PaymentMethodTypes { get; set; } + + public string Status { get; set; } + + public Dictionary Metadata { get; set; } +} diff --git a/src/Shopizy.Application/Common/models/CustomerResource.cs b/src/Shopizy.Application/Common/models/CustomerResource.cs new file mode 100644 index 0000000..1456d8e --- /dev/null +++ b/src/Shopizy.Application/Common/models/CustomerResource.cs @@ -0,0 +1,3 @@ +namespace Shopizy.Application.Common.models; + +public record CustomerResource(string CustomerId, string Email, string Name); diff --git a/src/Shopizy.Application/Common/models/Metadata.cs b/src/Shopizy.Application/Common/models/Metadata.cs new file mode 100644 index 0000000..0ce3eb1 --- /dev/null +++ b/src/Shopizy.Application/Common/models/Metadata.cs @@ -0,0 +1,7 @@ +namespace Shopizy.Application.Common.models; + +public class Metadata +{ + public required string OrderId { get; set; } + public Dictionary AdditionalData { get; set; } = []; +} diff --git a/src/Shopizy.Application/Common/models/PaymentResource.cs b/src/Shopizy.Application/Common/models/PaymentResource.cs deleted file mode 100644 index 521d34c..0000000 --- a/src/Shopizy.Application/Common/models/PaymentResource.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Shopizy.Application.Common.models; - -public record CustomerResource(string CustomerId, string Email, string Name); - -public record ChargeResource( - string ChargeId, - string Currency, - long Amount, - string CustomerId, - string ReceiptEmail, - string Description -); - -public record CheckoutSession(string SessionId, string PublishableKey); diff --git a/src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommand.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs similarity index 51% rename from src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommand.cs rename to src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs index 5091a69..8976897 100644 --- a/src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommand.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs @@ -4,13 +4,17 @@ using Shopizy.Application.Common.Security.Policies; using Shopizy.Application.Common.Security.Request; -namespace Shopizy.Application.Payments.Commands.CreatePaymentSession; +namespace Shopizy.Application.Payments.Commands.CardNotPresentSale; [Authorize(Permissions = Permissions.Order.Get, Policies = Policy.SelfOrAdmin)] -public record CreatePaymentSessionCommand( +public record CardNotPresentSaleCommand( Guid UserId, Guid OrderId, - string PaymentType, - string SuccessUrl, - string CancelUrl -) : IAuthorizeableRequest>; + double Amount, + string Currency, + string PaymentMethod, + string CardName, + string CardExpiryMonth, + string CardExpiryYear, + string LastDigits +) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs new file mode 100644 index 0000000..cc1217f --- /dev/null +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs @@ -0,0 +1,119 @@ +using ErrorOr; +using MediatR; +using Shopizy.Application.Common.Interfaces.Persistence; +using Shopizy.Application.Common.Interfaces.Services; +using Shopizy.Application.Common.models; +using Shopizy.Domain.Common.CustomErrors; +using Shopizy.Domain.Orders.ValueObjects; +using Shopizy.Domain.Payments; +using Shopizy.Domain.Payments.Enums; +using Shopizy.Domain.Users.ValueObjects; + +namespace Shopizy.Application.Payments.Commands.CardNotPresentSale; + +public class CardNotPresentSaleCommandHandler( + IPaymentRepository paymentRepository, + IOrderRepository orderRepository, + IUserRepository userRepository, + IPaymentService paymentService +) : IRequestHandler> +{ + private readonly IPaymentRepository _paymentRepository = paymentRepository; + private readonly IOrderRepository _orderRepository = orderRepository; + private readonly IUserRepository _userRepository = userRepository; + private readonly IPaymentService _paymentService = paymentService; + + public async Task> Handle( + CardNotPresentSaleCommand request, + CancellationToken cancellationToken + ) + { + try + { + var order = await _orderRepository.GetOrderByIdAsync(OrderId.Create(request.OrderId)); + + if (order is null) + { + return CustomErrors.Order.OrderNotFound; + } + + var total = order.GetTotal(); + + var user = await _userRepository.GetUserById(UserId.Create(request.UserId)); + + var payment = Payment.Create( + UserId.Create(request.UserId), + OrderId.Create(request.OrderId), + request.PaymentMethod, + "", + PaymentStatus.Pending, + total, + order.ShippingAddress + ); + + await _paymentRepository.AddAsync(payment); + + if (await _paymentRepository.Commit(cancellationToken) <= 0) + { + return CustomErrors.Payment.PaymentNotCreated; + } + + // Create customer id if not present already + if (user.CustomerId == null) + { + var customer = await _paymentService.CreateCustomer( + user.Email, + $"{user.FirstName} {user.LastName}", + cancellationToken + ); + + if (customer.IsError) + { + return CustomErrors.Payment.CustomerNotCreated; + } + + user.UpdateCustomerId(customer.Value.CustomerId); + + _userRepository.Update(user); + await _userRepository.Commit(cancellationToken); + } + + var req = new CreateSaleRequest() + { + CustomerId = user.CustomerId, + PaymentMethodId = request.PaymentMethod, + Amount = (long)(total.Amount * 100), + Currency = request.Currency, + PaymentMethodTypes = ["card"], + CapturePayment = true, + }; + + req.SetMetadata(new Metadata() { OrderId = order.Id.Value.ToString() }); + var response = await _paymentService.CreateSaleAsync(req); + + if (response.IsError) + { + return Error.Failure( + code: "payment.failed", + description: "Failed to collect the payment." + ); + } + + order.UpdatePaymentStatus(PaymentStatus.Payed); + payment.UpdatePaymentStatus(PaymentStatus.Payed); + + _orderRepository.Update(order); + _paymentRepository.Update(payment); + await _orderRepository.Commit(cancellationToken); + + return Result.Success; + } + catch (Exception ex) + { + return Error.Failure( + code: "payment.failed", + description: "Failed to collect the payment." + ); + } + } +} diff --git a/src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommandHandler.cs b/src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommandHandler.cs deleted file mode 100644 index 9d42e61..0000000 --- a/src/Shopizy.Application/Payments/Commands/CreatePaymentSession/CreatePaymentSessionCommandHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -using ErrorOr; -using MediatR; -using Shopizy.Application.Common.Interfaces.Persistence; -using Shopizy.Application.Common.Interfaces.Services; -using Shopizy.Application.Common.models; -using Shopizy.Domain.Common.CustomErrors; -using Shopizy.Domain.Orders.ValueObjects; -using Shopizy.Domain.Payments; -using Shopizy.Domain.Payments.Enums; -using Shopizy.Domain.Users.ValueObjects; - -namespace Shopizy.Application.Payments.Commands.CreatePaymentSession; - -public class CreatePaymentSessionCommandHandler( - IPaymentRepository paymentRepository, - IOrderRepository orderRepository, - IUserRepository userRepository, - IPaymentService paymentService -) : IRequestHandler> -{ - private readonly IPaymentRepository _paymentRepository = paymentRepository; - private readonly IOrderRepository _orderRepository = orderRepository; - private readonly IUserRepository _userRepository = userRepository; - private readonly IPaymentService _paymentService = paymentService; - - public async Task> Handle( - CreatePaymentSessionCommand request, - CancellationToken cancellationToken - ) - { - var order = await _orderRepository.GetOrderByIdAsync(OrderId.Create(request.OrderId)); - - if (order is null) - { - return CustomErrors.Order.OrderNotFound; - } - - var total = order.GetTotal(); - - var user = await _userRepository.GetUserById(UserId.Create(request.UserId)); - - var payment = Payment.Create( - UserId.Create(request.UserId), - OrderId.Create(request.OrderId), - request.PaymentType, - "", - PaymentStatus.Pending, - total, - order.ShippingAddress - ); - - await _paymentRepository.AddAsync(payment); - - if (await _paymentRepository.Commit(cancellationToken) <= 0) - { - return CustomErrors.Payment.PaymentNotCreated; - } - - return await _paymentService.CreateCheckoutSession( - user?.Email ?? "", - total.Amount, - request.SuccessUrl, - request.CancelUrl, - cancellationToken - ); - } -} diff --git a/src/Shopizy.Application/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.cs b/src/Shopizy.Application/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.cs index 20b6f25..4595898 100644 --- a/src/Shopizy.Application/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.cs +++ b/src/Shopizy.Application/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.cs @@ -20,7 +20,12 @@ public async Task> Handle( CancellationToken cancellationToken ) { - Domain.Users.User? user = await _userRepository.GetUserById(UserId.Create(request.UserId)); + if (request.OldPassword == request.NewPassword) + { + return CustomErrors.User.PasswordSameAsOld; + } + + var user = await _userRepository.GetUserById(UserId.Create(request.UserId)); if (user is null) { return CustomErrors.User.UserNotFound; diff --git a/src/Shopizy.Contracts/Order/CreateOrderRequest.cs b/src/Shopizy.Contracts/Order/CreateOrderRequest.cs index 69066ef..2bac78a 100644 --- a/src/Shopizy.Contracts/Order/CreateOrderRequest.cs +++ b/src/Shopizy.Contracts/Order/CreateOrderRequest.cs @@ -7,7 +7,7 @@ public record CreateOrderRequest( Address ShippingAddress ); -public record Price(decimal Amount, int Currency); +public record Price(decimal Amount, string Currency); public record OrderItemRequest(Guid ProductId, int Quantity); diff --git a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs new file mode 100644 index 0000000..06a856b --- /dev/null +++ b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs @@ -0,0 +1,12 @@ +namespace Shopizy.Contracts.Payment; + +public record CardNotPresentSaleRequest( + Guid OrderId, + double Amount, + string Currency, + string PaymentMethod, + string CardName, + string CardExpiryMonth, + string CardExpiryYear, + string LastDigits +); diff --git a/src/Shopizy.Contracts/Payment/CreatePaymentSessionRequest.cs b/src/Shopizy.Contracts/Payment/CreatePaymentSessionRequest.cs deleted file mode 100644 index c2084b1..0000000 --- a/src/Shopizy.Contracts/Payment/CreatePaymentSessionRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Shopizy.Contracts.Payment; - -public record CreatePaymentSessionRequest( - Guid OrderId, - string PaymentType, - string SuccessUrl, - string CancelUrl -); diff --git a/src/Shopizy.Contracts/Payment/PaymentSessionResponse.cs b/src/Shopizy.Contracts/Payment/PaymentSessionResponse.cs deleted file mode 100644 index 192666f..0000000 --- a/src/Shopizy.Contracts/Payment/PaymentSessionResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Shopizy.Contracts.Payment; - -public record PaymentSessionResponse(string SessionId, string PublishableKey); diff --git a/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.Payment.cs b/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.Payment.cs index 77ad642..60273dc 100644 --- a/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.Payment.cs +++ b/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.Payment.cs @@ -11,5 +11,10 @@ public static class Payment code: "payment.PaymentNotCreated", description: "Failed to create Payment." ); + public static Error CustomerNotCreated => + Error.Failure( + code: "payment.CustomerNotCreated", + description: "Failed to create customer." + ); } } diff --git a/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.User.cs b/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.User.cs index 61e4e1d..11f1205 100644 --- a/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.User.cs +++ b/src/Shopizy.Domain/Common/CustomErrors/CustomErrors.User.cs @@ -24,6 +24,11 @@ public static class User code: "User.PasswordNotCorrect", description: "Password is not correct." ); + public static Error PasswordSameAsOld => + Error.Validation( + code: "User.PasswordSameAsOld", + description: "Password is same as old password." + ); public static Error PasswordNotUpdated => Error.Validation( code: "User.PasswordNotUpdated", diff --git a/src/Shopizy.Domain/Orders/Order.cs b/src/Shopizy.Domain/Orders/Order.cs index 94680df..275cb2c 100644 --- a/src/Shopizy.Domain/Orders/Order.cs +++ b/src/Shopizy.Domain/Orders/Order.cs @@ -78,4 +78,10 @@ public Price GetTotal() return Price.CreateNew(chargeAmount, DeliveryCharge.Currency); } + + public void UpdatePaymentStatus(PaymentStatus status) + { + PaymentStatus = status; + ModifiedOn = DateTime.UtcNow; + } } diff --git a/src/Shopizy.Domain/Payments/Payment.cs b/src/Shopizy.Domain/Payments/Payment.cs index a9b1765..c00fe4a 100644 --- a/src/Shopizy.Domain/Payments/Payment.cs +++ b/src/Shopizy.Domain/Payments/Payment.cs @@ -54,7 +54,8 @@ private Payment( PaymentStatus paymentStatus, Price total, Address billingAddress - ) : base(PaymentId) + ) + : base(PaymentId) { UserId = userId; OrderId = orderId; @@ -67,4 +68,10 @@ Address billingAddress } private Payment() { } + + public void UpdatePaymentStatus(PaymentStatus status) + { + PaymentStatus = status; + ModifiedOn = DateTime.UtcNow; + } } diff --git a/src/Shopizy.Domain/Users/User.cs b/src/Shopizy.Domain/Users/User.cs index b54fb10..f547f8f 100644 --- a/src/Shopizy.Domain/Users/User.cs +++ b/src/Shopizy.Domain/Users/User.cs @@ -17,6 +17,7 @@ public sealed class User : AggregateRoot public string? ProfileImageUrl { get; } public string Phone { get; private set; } public string? Password { get; private set; } + public string? CustomerId { get; private set; } = null; public Address? Address { get; private set; } public DateTime CreatedOn { get; private set; } public DateTime? ModifiedOn { get; private set; } @@ -58,4 +59,10 @@ public void UpdateEmail(string email) Email = email; ModifiedOn = DateTime.UtcNow; } + + public void UpdateCustomerId(string customerId) + { + CustomerId = customerId; + ModifiedOn = DateTime.UtcNow; + } } diff --git a/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs b/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs index 3951b1c..fd6a0c4 100644 --- a/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs +++ b/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs @@ -100,7 +100,7 @@ IConfiguration configuration .AddScoped() .AddScoped() .AddScoped() - .AddScoped() + .AddScoped() .AddScoped(); services.Configure(configuration.GetSection(StripeSettings.Section)); diff --git a/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs b/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs index 43c9bb9..48298e2 100644 --- a/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs +++ b/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs @@ -1,21 +1,17 @@ using ErrorOr; -using Microsoft.Extensions.Options; using Shopizy.Application.Common.Interfaces.Services; using Shopizy.Application.Common.models; using Stripe; -using Stripe.Checkout; namespace Shopizy.Infrastructure.ExternalServices.PaymentGateway.Stripe; public class StripeService( CustomerService customerService, - SessionService sessionService, - IOptions options + PaymentIntentService paymentIntentService ) : IPaymentService { private readonly CustomerService _customerService = customerService; - private readonly SessionService _sessionService = sessionService; - private readonly StripeSettings _stripeSettings = options.Value; + private readonly PaymentIntentService _paymentIntentService = paymentIntentService; public async Task> CreateCustomer( string email, @@ -40,57 +36,67 @@ CancellationToken cancellationToken } } - public async Task> CreateCheckoutSession( - string customerEmail, - decimal price, - string successUrl, - string cancelUrl, - CancellationToken cancellationToken = default - ) + public async Task> CreateSaleAsync(CreateSaleRequest request) { - // var searchOptions = new CustomerSearchOptions { Query = $"email:'{customerEmail}'" }; - // var customer = await _customerService.SearchAsync(searchOptions, null, cancellationToken); + var intentCreateOptions = new PaymentIntentCreateOptions + { + Customer = request.CustomerId, + Amount = request.Amount, + Currency = request.Currency, + // ConfirmationMethod = "manual", + // Confirm = request.CapturePayment, + PaymentMethodTypes = request.PaymentMethodTypes, + Metadata = request.Metadata, + PaymentMethod = request.PaymentMethodId, + }; try { - var options = new SessionCreateOptions + var response = await _paymentIntentService.CreateAsync(intentCreateOptions); + + return new CreateSaleResponse { - Customer = customerEmail, - LineItems = - [ - new SessionLineItemOptions - { - PriceData = new SessionLineItemPriceDataOptions - { - ProductData = new SessionLineItemPriceDataProductDataOptions - { - Name = "test Name", - Description = "test description", - Images = - [ - "https://st2.depositphotos.com/2251265/8722/i/950/depositphotos_87226702-stock-photo-bearded-young-man-standing-on.jpg", - ], - }, - UnitAmountDecimal = price * 100, - Currency = "usd", - }, - Quantity = 1, - }, - ], - PaymentMethodTypes = ["card"], - Mode = "payment", - SuccessUrl = successUrl, - CancelUrl = cancelUrl, - AllowPromotionCodes = true, + ResponseStatusCode = (int)response.StripeResponse.StatusCode, + Amount = response.Amount, + Currency = response.Currency, + PaymentIntentId = response.Id, + ObjectType = response.Object, + PaymentMethodId = response.PaymentMethodId, + CaptureMethod = response.CaptureMethod, + CustomerId = response.CustomerId, + ChargeId = response.LatestChargeId, + Status = response.Status, + Metadata = response.Metadata, + PaymentMethodTypes = response.PaymentMethodTypes, }; - - Session session = await _sessionService.CreateAsync(options, null, cancellationToken); - - return new CheckoutSession(session.Id, _stripeSettings.PublishableKey); } catch (StripeException ex) { - return Error.Failure(description: ex.Message); + return Error.Failure( + code: ex.StripeResponse.StatusCode.ToString(), + description: FormatStripeException(ex) + ); } + catch (Exception ex) + { + return Error.Failure(code: "500", description: ex.Message); + } + } + + private static string FormatStripeException(StripeException e) + { + return e.StripeError.Type switch + { + "card_error" => $"A payment error occurred: {e.StripeError.Message}", + "api_connection_error" => + $"An error occurred while trying to connect to the stripe API: ${e.StripeError.Message}", + "api_error" => $"An API error occurred: {e.StripeError.Message}", + "authentication_error" => + $"An error occurred authenticating to Stripe API: {e.StripeError.Message}", + "invalid_request_error" => $"An invalid request occurred: {e.StripeError.Message}", + "rate_limit_error" => $"A rate limit error occurred: {e.StripeError.Message}", + "validation_error" => $"A validation error occurred: {e.StripeError.Message}", + _ => $"An unknown error occured: {e.StripeError.Message}", + }; } } diff --git a/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.Designer.cs new file mode 100644 index 0000000..aafa8b9 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.Designer.cs @@ -0,0 +1,753 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241201171255_AddCustomerIdInUserTable")] + partial class AddCustomerIdInUserTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("LineItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithOne() + .HasForeignKey("Shopizy.Domain.Payments.Payment", "OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithOne() + .HasForeignKey("Shopizy.Domain.Payments.Payment", "UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.cs b/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.cs new file mode 100644 index 0000000..fad4155 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241201171255_AddCustomerIdInUserTable.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class AddCustomerIdInUserTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CustomerId", + table: "Users", + type: "nvarchar(256)", + maxLength: 256, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CustomerId", + table: "Users"); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 4f7449e..5b4f4d9 100644 --- a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -275,6 +275,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedOn") .HasColumnType("smalldatetime"); + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + b.Property("Email") .HasColumnType("nvarchar(max)"); diff --git a/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs b/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs index 7c73b1c..8d43d39 100644 --- a/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs +++ b/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs @@ -46,6 +46,8 @@ private static void ConfigureUsersTable(EntityTypeBuilder builder) } ); + builder.Property(u => u.CustomerId).HasMaxLength(256).IsRequired(false); + builder.Navigation(p => p.Orders).UsePropertyAccessMode(PropertyAccessMode.Field); builder.Navigation(p => p.ProductReviews).UsePropertyAccessMode(PropertyAccessMode.Field); } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdateAddress/UpdateAddressCommandHadler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdateAddress/UpdateAddressCommandHadler.test.cs index 738a6d5..a6f604e 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdateAddress/UpdateAddressCommandHadler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdateAddress/UpdateAddressCommandHadler.test.cs @@ -4,6 +4,7 @@ using Shopizy.Application.Common.Interfaces.Persistence; using Shopizy.Application.UnitTests.Users.TestUtils; using Shopizy.Application.Users.Commands.UpdateAddress; +using Shopizy.Domain.Common.CustomErrors; using Shopizy.Domain.Users; using Shopizy.Domain.Users.ValueObjects; @@ -20,57 +21,29 @@ public UpdateAddressCommandHandlerTests() _sut = new UpdateAddressCommandHandler(_mockUserRepository.Object); } - // [Fact] - // public async Task ShouldThrowExceptionWhenUserIdIsNotProvided() - // { - // // Arrange - // var mockUserRepository = new Mock(); - // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); - // var command = new UpdateAddressCommand - // { - // UserId = null, // Invalid user ID - // Street = "123 Main St", - // City = "New York", - // State = "NY", - // Country = "USA", - // ZipCode = "10001", - // }; - - // // Act - // Func>> act = async () => - // await handler.Handle(command, CancellationToken.None); - - // // Assert - // await Assert.ThrowsAsync(act); - // } + [Fact] + public async Task ShouldReturnErrorResponseWhenUserIsNotFoundAsync() + { + // Arrange + var command = UpdateAddressCommandUtils.CreateCommand(); - // [Fact] - // public async Task ShouldReturnErrorResponseWhenUserIsNotFound() - // { - // // Arrange - // var mockUserRepository = new Mock(); - // mockUserRepository - // .Setup(repo => repo.GetUserById(It.IsAny())) - // .ReturnsAsync((Domain.Users.User)null); + _mockUserRepository + .Setup(repo => repo.GetUserById(UserId.Create(command.UserId))) + .ReturnsAsync(() => null); - // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); - // var command = new UpdateAddressCommand - // { - // UserId = "123", - // Street = "123 Main St", - // City = "New York", - // State = "NY", - // Country = "USA", - // ZipCode = "10001", - // }; + // Act + var result = await _sut.Handle(command, CancellationToken.None); - // // Act - // var result = await handler.Handle(command, CancellationToken.None); + // Assert + result.IsError.Should().BeTrue(); + result.Errors.Should().NotBeNullOrEmpty(); + result.Errors.Should().HaveCount(1); + result.Errors[0].Should().BeEquivalentTo(CustomErrors.User.UserNotFound); - // // Assert - // result.Should().BeOfType>(); - // result.Errors.Should().Contain(CustomErrors.User.UserNotFound); - // } + _mockUserRepository.Verify(x => x.GetUserById(UserId.Create(command.UserId)), Times.Once); + _mockUserRepository.Verify(x => x.Update(It.IsAny()), Times.Never); + _mockUserRepository.Verify(x => x.Commit(It.IsAny()), Times.Never); + } [Fact] public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() @@ -80,9 +53,11 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() user = UserFactory.UpdateAddress(user); var command = UpdateAddressCommandUtils.CreateCommand(); - _mockUserRepository.Setup(u => u.GetUserById(It.IsAny())).ReturnsAsync(user); + _mockUserRepository + .Setup(u => u.GetUserById(UserId.Create(command.UserId))) + .ReturnsAsync(user); - _mockUserRepository.Setup(u => u.Update(It.IsAny())); + _mockUserRepository.Setup(u => u.Update(user)); _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); @@ -99,61 +74,46 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() _mockUserRepository.Verify(x => x.Commit(It.IsAny()), Times.Once); } - // [Fact] - // public async Task ShouldHandleConcurrentUpdatesToTheSameUser() - // { - // // Arrange - // var mockUserRepository = new Mock(); - // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); - // var command = new UpdateAddressCommand - // { - // UserId = "123", - // Street = "123 Main St", - // City = "New York", - // State = "NY", - // Country = "USA", - // ZipCode = "10001", - // }; + [Fact] + public async Task ShouldHandleConcurrentUpdatesToTheSameUserAsync() + { + // Arrange - // mockUserRepository - // .Setup(repo => repo.GetUserById(It.IsAny())) - // .ReturnsAsync( - // new Domain.Users.User( - // userId: UserId.Create("123"), - // name: "John Doe", - // email: "johndoe@example.com", - // phoneNumber: "1234567890", - // address: Address.CreateNew( - // "123 Old St", - // "Old City", - // "Old State", - // "Old Country", - // "10000" - // ) - // ) - // ); + var command = UpdateAddressCommandUtils.CreateCommand(); + var user = UserFactory.CreateUser(); - // var user1 = mockUserRepository.Object.GetUserById(UserId.Create("123")).Result; - // var user2 = mockUserRepository.Object.GetUserById(UserId.Create("123")).Result; + _mockUserRepository + .Setup(repo => repo.GetUserById(UserId.Create(command.UserId))) + .ReturnsAsync(user); - // // Act - // var task1 = handler.Handle(command, CancellationToken.None); - // var task2 = handler.Handle(command, CancellationToken.None); + _mockUserRepository.Setup(u => u.Update(It.IsAny())); - // await Task.WhenAll(task1, task2); + _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); - // // Assert - // var result1 = task1.Result; - // var result2 = task2.Result; + // Act + var task1 = _sut.Handle(command, CancellationToken.None); + var task2 = _sut.Handle(command, CancellationToken.None); - // result1.Should().BeOfType>(); - // result2.Should().BeOfType>(); - // result1.Value.Message.Should().Be("Successfully updated address."); - // result2.Value.Message.Should().Be("Successfully updated address."); - // } + await Task.WhenAll(task1, task2); + + // Assert + var result1 = task1.Result; + var result2 = task2.Result; + + result1.Should().BeOfType>(); + result2.Should().BeOfType>(); + result1.IsError.Should().BeFalse(); + result2.IsError.Should().BeFalse(); + + result1.Value.Should().BeOfType(); + result1.Value.Should().NotBeNull(); + + result2.Value.Should().BeOfType(); + result2.Value.Should().NotBeNull(); + } // [Fact] - // public async Task ShouldValidateAddressFieldsForCorrectFormatAndLength() + // public async Task ShouldValidateAddressFieldsForCorrectFormatAndLengthAsync() // { // // Arrange // var mockUserRepository = new Mock(); @@ -194,68 +154,35 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() // result.Value.Message.Should().Be("Successfully updated address."); // } - // [Fact] - // public async Task ShouldHandleCasesWhereDatabaseConnectionIsLostDuringCommit() - // { - // // Arrange - // var mockUserRepository = new Mock(); - // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); - // var command = new UpdateAddressCommand - // { - // UserId = "123", - // Street = "123 Main St", - // City = "New York", - // State = "NY", - // Country = "USA", - // ZipCode = "10001", - // }; + [Fact] + public async Task ShouldHandleCasesWhereDatabaseConnectionIsLostDuringCommitAsync() + { + // Arrange - // mockUserRepository - // .Setup(repo => repo.GetUserById(It.IsAny())) - // .ReturnsAsync( - // new Domain.Users.User( - // userId: UserId.Create("123"), - // name: "John Doe", - // email: "johndoe@example.com", - // phoneNumber: "1234567890", - // address: Address.CreateNew( - // "123 Old St", - // "Old City", - // "Old State", - // "Old Country", - // "10000" - // ) - // ) - // ); + var command = UpdateAddressCommandUtils.CreateCommand(); + var user = UserFactory.CreateUser(); - // mockUserRepository - // .Setup(repo => repo.Update(It.IsAny())) - // .Callback(user => - // user.UpdateAddress( - // Address.CreateNew( - // street: command.Street, - // city: command.City, - // state: command.State, - // country: command.Country, - // zipCode: command.ZipCode - // ) - // ) - // ); + _mockUserRepository.Setup(repo => repo.GetUserById(It.IsAny())).ReturnsAsync(user); - // mockUserRepository - // .Setup(repo => repo.Commit(It.IsAny())) - // .ReturnsAsync(0); // Simulate database connection loss + _mockUserRepository + .Setup(u => u.GetUserById(UserId.Create(command.UserId))) + .ReturnsAsync(user); - // // Act - // var result = await handler.Handle(command, CancellationToken.None); + _mockUserRepository + .Setup(repo => repo.Commit(It.IsAny())) + .ReturnsAsync(0); // Simulate database connection loss - // // Assert - // result.Should().BeOfType>(); - // result.Errors.Should().Contain(CustomErrors.User.UserNotUpdated); - // } + // Act + var result = await _sut.Handle(command, CancellationToken.None); + + // Assert + result.IsError.Should().BeTrue(); + result.Errors.Should().NotBeNullOrEmpty(); + result.Errors[0].Should().Be(CustomErrors.User.UserNotUpdated); + } // [Fact] - // public async Task ShouldReturnErrorResponseWhenDatabaseIsReadOnly() + // public async Task ShouldReturnErrorResponseWhenDatabaseIsReadOnlyAsync() // { // // Arrange // var mockUserRepository = new Mock(); @@ -283,11 +210,10 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() // } // [Fact] - // public async Task ShouldNotUpdateUserAddressWhenAddressIsUnchanged() + // public async Task ShouldNotUpdateUserAddressWhenAddressIsUnchangedAsync() // { // // Arrange - // var mockUserRepository = new Mock(); - // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); + // var command = new UpdateAddressCommand // { // UserId = "123", @@ -343,7 +269,7 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() // } // [Fact] - // public async Task ShouldHandlePartialAddressChange() + // public async Task ShouldHandlePartialAddressChangeAsync() // { // // Arrange // var mockUserRepository = new Mock(); @@ -405,7 +331,7 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() // [Fact] // public async Task ShouldHandleCasesWhereUserAddressIsUpdatedToExistingAddress() // { - // // Arrange + // Arrange // var mockUserRepository = new Mock(); // var handler = new UpdateAddressCommandHandler(mockUserRepository.Object); // var command = new UpdateAddressCommand @@ -448,10 +374,10 @@ public async Task ShouldUpdateUserAddressSuccessfullyWhenUserIsFoundAsync() // .Setup(repo => repo.Commit(It.IsAny())) // .ReturnsAsync(1); - // // Act + // Act // var result = await handler.Handle(command, CancellationToken.None); - // // Assert + // Assert // result.Should().BeOfType>(); // result.Value.Message.Should().Be("Successfully updated address."); // } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs index e77f2ef..4874804 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs @@ -27,30 +27,24 @@ public UpdatePasswordCommandHandlerTests() ); } - // [Fact] - // public async Task ShouldThrowExceptionWhenUserIdIsNotProvided() - // { - // // Arrange - // var userRepositoryMock = new Mock(); - // var passwordManagerMock = new Mock(); - // var handler = new UpdatePasswordCommandHandler( - // userRepositoryMock.Object, - // passwordManagerMock.Object - // ); - // var command = new UpdatePasswordCommand - // { - // UserId = "", - // OldPassword = "oldPassword", - // NewPassword = "newPassword", - // }; + [Fact] + public async Task ShouldThrowExceptionWhenUserIdIsNotProvidedAsync() + { + // Arrange + var command = UpdatePasswordCommandUtils.CreateCommandWithEmptyUserId(); - // // Act - // var result = await handler.Handle(command, CancellationToken.None); + _mockUserRepository + .Setup(u => u.GetUserById(UserId.Create(command.UserId))) + .ReturnsAsync(() => null); - // // Assert - // Assert.True(result.IsFailure); - // Assert.Contains(CustomErrors.User.UserNotFound, result.Errors); - // } + // Act + var result = await _sut.Handle(command, CancellationToken.None); + + // Assert + result.IsError.Should().BeTrue(); + result.Errors.Should().NotBeNullOrEmpty(); + result.Errors[0].Should().Be(CustomErrors.User.UserNotFound); + } [Fact] public async Task ShouldReturnErrorWhenUserIsNotFoundAsync() @@ -145,36 +139,41 @@ public async Task ShouldUpdatePasswordWhenOldPasswordIsCorrectAndNewPasswordIsPr _mockUserRepository.Verify(x => x.Commit(It.IsAny()), Times.Once); } - // [Fact] - // public async Task ShouldReturnErrorWhenNewPasswordIsSameAsOldPassword() - // { - // // Arrange - // var userRepositoryMock = new Mock(); - // var passwordManagerMock = new Mock(); - // var handler = new UpdatePasswordCommandHandler( - // userRepositoryMock.Object, - // passwordManagerMock.Object - // ); - // var command = new UpdatePasswordCommand - // { - // UserId = "123", - // OldPassword = "oldPassword", - // NewPassword = "oldPassword", - // }; - // userRepositoryMock - // .Setup(x => x.GetUserById(It.IsAny())) - // .ReturnsAsync(new Domain.Users.User(UserId.Create("123"), "oldPassword")); - // passwordManagerMock - // .Setup(x => x.Verify(It.IsAny(), It.IsAny())) - // .Returns(true); + [Fact] + public async Task ShouldReturnErrorWhenNewPasswordIsSameAsOldPasswordAsync() + { + // Arrange + var command = UpdatePasswordCommandUtils.CreateCommandWithSameOldAndNewPassword(); + var user = UserFactory.CreateUser(); + var updatedUser = UserFactory.UpdatePassword(user); - // // Act - // var result = await handler.Handle(command, CancellationToken.None); + _mockUserRepository.Setup(x => x.GetUserById(It.IsAny())).ReturnsAsync(user); - // // Assert - // Assert.True(result.IsFailure); - // Assert.Contains(CustomErrors.User.PasswordCannotBeSame, result.Errors); - // } + _mockPasswordManager + .Setup(x => x.Verify(It.IsAny(), It.IsAny())) + .Returns(true); + + _mockPasswordManager + .Setup(u => u.CreateHashString(It.IsAny(), 10000)) + .Returns("hashstring"); + + _mockUserRepository.Setup(u => u.Update(updatedUser)); + + _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); + + // Act + var result = await _sut.Handle(command, CancellationToken.None); + + // Assert + result.IsError.Should().BeTrue(); + result.Should().BeOfType>(); + result.Errors.Should().NotBeNullOrEmpty(); + result.Errors[0].Should().Be(CustomErrors.User.PasswordSameAsOld); + + _mockUserRepository.Verify(x => x.GetUserById(It.IsAny()), Times.Never); + _mockUserRepository.Verify(x => x.Update(user), Times.Never); + _mockUserRepository.Verify(x => x.Commit(It.IsAny()), Times.Never); + } // [Fact] // public async Task ShouldReturnErrorWhenNewPasswordIsTooShort() diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs index 026af52..d9708ce 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs @@ -13,4 +13,22 @@ public static UpdatePasswordCommand CreateCommand() Constants.User.NewPassword ); } + + public static UpdatePasswordCommand CreateCommandWithSameOldAndNewPassword() + { + return new UpdatePasswordCommand( + Constants.User.Id.Value, + Constants.User.Password, + Constants.User.Password + ); + } + + public static UpdatePasswordCommand CreateCommandWithEmptyUserId() + { + return new UpdatePasswordCommand( + Constants.User.Id.Value, + Constants.User.Password, + Constants.User.Password + ); + } } From 1dd93c9cef2ef971e89226dafa82922cb98cd543 Mon Sep 17 00:00:00 2001 From: "Md. Abul Kalam" Date: Thu, 12 Dec 2024 23:21:28 +0600 Subject: [PATCH 2/4] Issue #56: Refactor and added some tests. --- .../Common/Mapping/CartMappingConfig.cs | 6 +- .../Common/Mapping/ProductMappingConfig.cs | 14 + .../Controllers/ProductController.cs | 6 +- .../Controllers/WebhooksController.cs | 64 -- .../AddProductToCartCommand.cs | 9 +- .../AddProductToCartCommandHandler.cs | 4 +- .../CreateCartWithFirstProductCommand.cs | 8 +- ...reateCartWithFirstProductCommandHandler.cs | 2 +- .../RemoveProductFromCartCommandHandler.cs | 2 +- .../Persistence/IProductRepository.cs | 2 +- .../CreateOrder/CreateOrderCommand.cs | 3 +- .../CreateOrder/CreateOrderCommandHandler.cs | 28 +- .../CardNotPresentSaleCommand.cs | 1 + .../CardNotPresentSaleCommandHandler.cs | 4 +- .../CreateProduct/CreateProductCommand.cs | 3 + .../CreateProductCommandHandler.cs | 21 +- .../UpdateProduct/UpdateProductCommand.cs | 3 + .../UpdateProductCommandHandler.cs | 21 +- .../Queries/GetProducts/GetProductsQuery.cs | 2 +- .../Cart/AddProductToCartRequest.cs | 2 +- src/Shopizy.Contracts/Cart/CartResponse.cs | 12 +- .../Cart/CreateCartWithFirstProductRequest.cs | 2 +- .../Order/CreateOrderRequest.cs | 3 +- .../Payment/CardNotPresentSaleRequest.cs | 1 + .../Product/CreateProductRequest.cs | 3 + .../Product/ProductResponse.cs | 39 +- .../Product/ProductsCriteriaRequest.cs | 2 +- .../Product/UpdateProductRequest.cs | 3 + src/Shopizy.Domain/Carts/Cart.cs | 16 +- .../Entities/{LineItem.cs => CartItem.cs} | 15 +- .../{LineItemId.cs => CartItemId.cs} | 8 +- .../Common/ValueObjects/AverageRating.cs | 6 +- .../Common/ValueObjects/Ratings.cs | 6 +- .../Orders/Entities/OrderItem.cs | 15 +- src/Shopizy.Domain/Orders/Enums/OderStatus.cs | 10 +- src/Shopizy.Domain/Orders/Order.cs | 5 + src/Shopizy.Domain/Payments/Payment.cs | 11 + .../ProductReviews/ProductReview.cs | 5 +- src/Shopizy.Domain/Products/Product.cs | 29 +- .../Carts/Persistence/CartConfigurations.cs | 26 +- .../Carts/Persistence/CartRepository.cs | 2 +- .../EventualConsistencyMiddleware.cs | 8 +- .../Common/SeedData/ProductImages.json | 7 + .../Common/SeedData/Products.json | 10 + .../Common/SeedData/SeedData.SQL | 32 +- .../PaymentGateway/Stripe/StripeService.cs | 6 +- ...41202145010_UpdatePaymentTable.Designer.cs | 758 +++++++++++++++++ .../20241202145010_UpdatePaymentTable.cs | 52 ++ ...02154947_UpdatePaymentTableRef.Designer.cs | 756 +++++++++++++++++ .../20241202154947_UpdatePaymentTableRef.cs | 56 ++ ...41211115321_UpdateRatingColumn.Designer.cs | 758 +++++++++++++++++ .../20241211115321_UpdateRatingColumn.cs | 70 ++ ...41211135310_UpdateProductTable.Designer.cs | 776 +++++++++++++++++ .../20241211135310_UpdateProductTable.cs | 83 ++ .../20241212165458_UpdateDatabase.Designer.cs | 795 ++++++++++++++++++ .../20241212165458_UpdateDatabase.cs | 139 +++ .../Migrations/AppDbContextModelSnapshot.cs | 82 +- .../Persistence/PaymentConfigurations.cs | 7 +- .../Persistence/ProductConfigurations.cs | 5 +- .../Products/Persistence/ProductRepository.cs | 7 +- .../Specifications/ProductsByCriteriaSpec.cs | 2 +- .../AddProductToCartCommandHandler.test.cs | 41 +- ...CartWithFirstProductCommandHandler.test.cs | 8 +- ...emoveProductFromCartCommandHandler.test.cs | 2 +- .../GetCart/GetCartQueryHandler.test.cs | 2 +- .../TestUtils/AddProductToCartCommandUtils.cs | 4 +- .../Carts/TestUtils/CartFactory.cs | 8 +- .../CreateCartWithFirstProductCommandUtils.cs | 4 +- .../TestUtils/CreateOrderCommandUtils.cs | 8 +- .../Orders/TestUtils/OrderFactory.cs | 1 + .../GetProductsQueryHandler.test.cs | 2 +- .../TestUtils/CreateProductCommandUtils.cs | 6 + .../Products/TestUtils/ProductFactory.cs | 3 + .../TestUtils/UpdateProductCommandUtils.cs | 3 + .../TestUtils/Constants/Constants.Cart.cs | 6 +- .../TestUtils/Constants/Constants.Order.cs | 3 + .../TestUtils/Constants/Constants.Product.cs | 3 + .../TestUtils/Constants/Constants.User.cs | 4 +- .../Extensions/CartExtensions.Validations.cs | 2 +- .../UpdatePasswordCommandHadler.test.cs | 41 +- .../TestUtils/UpdatePasswordCommandUtils.cs | 9 - .../Users/TestUtils/UserFactory.cs | 6 - 82 files changed, 4725 insertions(+), 274 deletions(-) delete mode 100644 src/Shopizy.Api/Controllers/WebhooksController.cs rename src/Shopizy.Domain/Carts/Entities/{LineItem.cs => CartItem.cs} (51%) rename src/Shopizy.Domain/Carts/ValueObjects/{LineItemId.cs => CartItemId.cs} (67%) create mode 100644 src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs diff --git a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs index d81c71b..5050420 100644 --- a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs @@ -55,11 +55,11 @@ public void Register(TypeAdapterConfig config) .NewConfig() .Map(dest => dest.CartId, src => src.Id.Value) .Map(dest => dest.UserId, src => src.UserId.Value) - .Map(dest => dest.LineItems, src => src.LineItems); + .Map(dest => dest.CartItems, src => src.CartItems); config - .NewConfig() - .Map(dest => dest.LineItemId, src => src.Id.Value) + .NewConfig() + .Map(dest => dest.CartItemId, src => src.Id.Value) .Map(dest => dest.ProductId, src => src.ProductId.Value) .Map(dest => dest.Quantity, src => src.Quantity) .Map(dest => dest.Product, src => src.Product) diff --git a/src/Shopizy.Api/Common/Mapping/ProductMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/ProductMappingConfig.cs index b8bb670..b500454 100644 --- a/src/Shopizy.Api/Common/Mapping/ProductMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/ProductMappingConfig.cs @@ -8,6 +8,7 @@ using Shopizy.Application.Products.Queries.GetProducts; using shopizy.Contracts.Product; using Shopizy.Contracts.Product; +using Shopizy.Domain.ProductReviews; using Shopizy.Domain.Products; using Shopizy.Domain.Products.Entities; @@ -46,6 +47,12 @@ public void Register(TypeAdapterConfig config) .NewConfig() .Map(dest => dest.ProductId, src => src.Id.Value) .Map(dest => dest.CategoryId, src => src.CategoryId.Value) + .Map(dest => dest.Price, src => src.UnitPrice.Amount.ToString()); + + config + .NewConfig() + .Map(dest => dest.ProductId, src => src.Id.Value) + .Map(dest => dest.CategoryId, src => src.CategoryId.Value) .Map(dest => dest.Sku, src => src.SKU) .Map(dest => dest.Price, src => src.UnitPrice.Amount.ToString()); @@ -53,6 +60,13 @@ public void Register(TypeAdapterConfig config) .NewConfig() .Map(dest => dest.ProductImageId, src => src.Id.Value); + config + .NewConfig() + .Map(dest => dest.ProductReviewId, src => src.Id.Value) + .Map(dest => dest.Rating, src => src.Rating.Value) + .Map(dest => dest.Reviewer, src => $"{src.User.FirstName} {src.User.LastName}") + .Map(dest => dest.ReviewerImageUrl, src => src.User.ProfileImageUrl); + config .NewConfig<(Guid UserId, Guid ProductId, Guid ImageId), DeleteProductImageCommand>() .Map(dest => dest.UserId, src => src.UserId) diff --git a/src/Shopizy.Api/Controllers/ProductController.cs b/src/Shopizy.Api/Controllers/ProductController.cs index f7ed802..7a6c0a6 100644 --- a/src/Shopizy.Api/Controllers/ProductController.cs +++ b/src/Shopizy.Api/Controllers/ProductController.cs @@ -26,7 +26,7 @@ public class ProductController(ISender mediator, IMapper mapper) : ApiController [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status500InternalServerError, null, typeof(ErrorResult))] - public async Task GetAsync([FromQuery] ProductsCriteriaRequest request) + public async Task SearchAsync([FromQuery] ProductsCriteriaRequest request) { var query = _mapper.Map(request); var result = await _mediator.Send(query); @@ -35,7 +35,7 @@ public async Task GetAsync([FromQuery] ProductsCriteriaRequest re } [HttpGet("products/{productId:guid}")] - [SwaggerResponse(StatusCodes.Status200OK, null, typeof(ProductResponse))] + [SwaggerResponse(StatusCodes.Status200OK, null, typeof(ProductDetailResponse))] [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status500InternalServerError, null, typeof(ErrorResult))] @@ -44,7 +44,7 @@ public async Task GetProductAsync(Guid ProductId) var query = _mapper.Map(ProductId); var result = await _mediator.Send(query); - return result.Match(product => Ok(_mapper.Map(product)), Problem); + return result.Match(product => Ok(_mapper.Map(product)), Problem); } [HttpPost("users/{userId:guid}/products")] diff --git a/src/Shopizy.Api/Controllers/WebhooksController.cs b/src/Shopizy.Api/Controllers/WebhooksController.cs deleted file mode 100644 index 4b5a721..0000000 --- a/src/Shopizy.Api/Controllers/WebhooksController.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Shopizy.Infrastructure.ExternalServices.PaymentGateway.Stripe; -using Stripe; - -namespace Shopizy.Api.Controllers; - -[Route("api/v1.0/webhooks")] -public class Webhooks(IOptions options) : ApiController -{ - private readonly StripeSettings _stripeSettings = options.Value; - - [HttpPost("stripe")] - public async Task StripeWebhookAsync() - { - string json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); - Event stripeEvent; - try - { - string webhookSecret = _stripeSettings.WebhookSecret; - stripeEvent = EventUtility.ConstructEvent( - json, - Request.Headers["Stripe-Signature"].ToString(), - webhookSecret - ); - Console.WriteLine( - $"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}" - ); - - if (stripeEvent.Type == EventTypes.PaymentIntentSucceeded) - { - // Some code here - } - else if (stripeEvent.Type == EventTypes.CustomerCreated) - { - if (stripeEvent.Data.Object is Customer) - { - // var result = await _accountService.UpdateStripeCustomerId( - // customer.Email, - // customer.Id - // ); - // if (!result) - // { - // Console.WriteLine($"Failed to add stripe customer id {customer.Id} to user"); - // } - } - } - else if (stripeEvent.Type == EventTypes.CheckoutSessionCompleted) { } - else if (stripeEvent.Type == EventTypes.InvoicePaid) { } - else if (stripeEvent.Type == EventTypes.InvoicePaymentFailed) { } - else - { - Console.WriteLine("Default"); - } - } - catch (Exception e) - { - Console.WriteLine($"Something failed {e}"); - return BadRequest(); - } - - return Ok(); - } -} diff --git a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommand.cs b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommand.cs index c1f308c..95ed792 100644 --- a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommand.cs +++ b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommand.cs @@ -7,5 +7,10 @@ namespace Shopizy.Application.Carts.Commands.AddProductToCart; [Authorize(Permissions = Permissions.Cart.Modify, Policies = Policy.SelfOrAdmin)] -public record AddProductToCartCommand(Guid UserId, Guid CartId, Guid ProductId) - : IAuthorizeableRequest>; +public record AddProductToCartCommand( + Guid UserId, + Guid CartId, + Guid ProductId, + string Color, + string Size +) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs index 8f896e6..3c10af4 100644 --- a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs @@ -33,7 +33,7 @@ CancellationToken cancellationToken return CustomErrors.Cart.CartNotFound; } - if (cart.LineItems.Any(li => li.ProductId == ProductId.Create(cmd.ProductId))) + if (cart.CartItems.Any(li => li.ProductId == ProductId.Create(cmd.ProductId))) { return CustomErrors.Cart.ProductAlreadyExistInCart; } @@ -45,7 +45,7 @@ CancellationToken cancellationToken return CustomErrors.Product.ProductNotFound; } - cart.AddLineItem(LineItem.Create(ProductId.Create(cmd.ProductId))); + cart.AddLineItem(CartItem.Create(ProductId.Create(cmd.ProductId), cmd.Color, cmd.Size)); _cartRepository.Update(cart); diff --git a/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommand.cs b/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommand.cs index f18c9a3..509f900 100644 --- a/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommand.cs +++ b/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommand.cs @@ -7,5 +7,9 @@ namespace Shopizy.Application.Carts.Commands.CreateCartWithFirstProduct; [Authorize(Permissions = Permissions.Cart.Create, Policies = Policy.SelfOrAdmin)] -public record CreateCartWithFirstProductCommand(Guid UserId, Guid ProductId) - : IAuthorizeableRequest>; +public record CreateCartWithFirstProductCommand( + Guid UserId, + Guid ProductId, + string Color, + string Size +) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.cs index 35ad8b7..87455fb 100644 --- a/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.cs @@ -30,7 +30,7 @@ CancellationToken cancellationToken } var cart = Cart.Create(UserId.Create(cmd.UserId)); - cart.AddLineItem(LineItem.Create(ProductId.Create(cmd.ProductId))); + cart.AddLineItem(CartItem.Create(ProductId.Create(cmd.ProductId), cmd.Color, cmd.Size)); await _cartRepository.AddAsync(cart); diff --git a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs index 6dd9e16..00018e3 100644 --- a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs @@ -26,7 +26,7 @@ CancellationToken cancellationToken return CustomErrors.Cart.CartNotFound; } - var lineItem = cart.LineItems.FirstOrDefault(li => li.ProductId.Value == cmd.ProductId); + var lineItem = cart.CartItems.FirstOrDefault(li => li.ProductId.Value == cmd.ProductId); if (lineItem is not null) { cart.RemoveLineItem(lineItem); diff --git a/src/Shopizy.Application/Common/Interfaces/Persistence/IProductRepository.cs b/src/Shopizy.Application/Common/Interfaces/Persistence/IProductRepository.cs index e2db334..2391e81 100644 --- a/src/Shopizy.Application/Common/Interfaces/Persistence/IProductRepository.cs +++ b/src/Shopizy.Application/Common/Interfaces/Persistence/IProductRepository.cs @@ -9,7 +9,7 @@ public interface IProductRepository Task> GetProductsAsync( string? name, IList? categoryIds, - double? averageRating, + decimal? averageRating, int pageNumber, int pageSize ); diff --git a/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs b/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs index df49ecf..ab74d26 100644 --- a/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs +++ b/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommand.cs @@ -11,13 +11,14 @@ namespace Shopizy.Application.Orders.Commands.CreateOrder; public record CreateOrderCommand( Guid UserId, string PromoCode, + int DeliveryMethod, decimal DeliveryChargeAmount, Currency DeliveryChargeCurrency, IEnumerable OrderItems, AddressCommand ShippingAddress ) : IAuthorizeableRequest>; -public record OrderItemCommand(Guid ProductId, int Quantity); +public record OrderItemCommand(Guid ProductId, string Color, string Size, int Quantity); public record AddressCommand( string Street, diff --git a/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs b/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs index d963298..c9e09bf 100644 --- a/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs +++ b/src/Shopizy.Application/Orders/Commands/CreateOrder/CreateOrderCommandHandler.cs @@ -43,6 +43,7 @@ CancellationToken cancellationToken var order = Order.Create( userId: UserId.Create(request.UserId), promoCode: request.PromoCode, + deliveryMethod: request.DeliveryMethod, deliveryCharge: Price.CreateNew( request.DeliveryChargeAmount, request.DeliveryChargeCurrency @@ -54,17 +55,22 @@ CancellationToken cancellationToken country: request.ShippingAddress.Country, zipCode: request.ShippingAddress.ZipCode ), - orderItems: products.ConvertAll(items => - OrderItem.Create( - name: items.Name, - pictureUrl: items.ProductImages.Count == 0 - ? "" - : items.ProductImages[0].ImageUrl, - unitPrice: items.UnitPrice, - quantity: request.OrderItems.First(p => p.ProductId == items.Id.Value).Quantity, - discount: items.Discount - ) - ) + orderItems: products.ConvertAll(product => + { + var item = request.OrderItems.First(p => p.ProductId == product.Id.Value); + var photoUrl = + product.ProductImages.Count == 0 ? "" : product.ProductImages[0].ImageUrl; + + return OrderItem.Create( + name: product.Name, + pictureUrl: photoUrl, + unitPrice: product.UnitPrice, + quantity: item.Quantity, + color: item.Color, + size: item.Size, + discount: product.Discount + ); + }) ); await _orderRepository.AddAsync(order); diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs index 8976897..7bd9213 100644 --- a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs @@ -13,6 +13,7 @@ public record CardNotPresentSaleCommand( double Amount, string Currency, string PaymentMethod, + string PaymentMethodId, string CardName, string CardExpiryMonth, string CardExpiryYear, diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs index cc1217f..30abdcf 100644 --- a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs @@ -45,6 +45,7 @@ CancellationToken cancellationToken UserId.Create(request.UserId), OrderId.Create(request.OrderId), request.PaymentMethod, + request.PaymentMethodId, "", PaymentStatus.Pending, total, @@ -81,7 +82,7 @@ CancellationToken cancellationToken var req = new CreateSaleRequest() { CustomerId = user.CustomerId, - PaymentMethodId = request.PaymentMethod, + PaymentMethodId = request.PaymentMethodId, Amount = (long)(total.Amount * 100), Currency = request.Currency, PaymentMethodTypes = ["card"], @@ -101,6 +102,7 @@ CancellationToken cancellationToken order.UpdatePaymentStatus(PaymentStatus.Payed); payment.UpdatePaymentStatus(PaymentStatus.Payed); + payment.UpdateTransactionId(response.Value.ChargeId); _orderRepository.Update(order); _paymentRepository.Update(payment); diff --git a/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommand.cs b/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommand.cs index 11e29c2..3632cf2 100644 --- a/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommand.cs +++ b/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommand.cs @@ -11,6 +11,7 @@ namespace Shopizy.Application.Products.Commands.CreateProduct; public record CreateProductCommand( Guid UserId, string Name, + string ShortDescription, string Description, Guid CategoryId, decimal UnitPrice, @@ -18,6 +19,8 @@ public record CreateProductCommand( decimal Discount, string Sku, string Brand, + string Colors, + string Sizes, string Tags, string Barcode, IList? SpecificationIds diff --git a/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommandHandler.cs b/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommandHandler.cs index 8c22edc..8b60261 100644 --- a/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommandHandler.cs +++ b/src/Shopizy.Application/Products/Commands/CreateProduct/CreateProductCommandHandler.cs @@ -19,15 +19,18 @@ CancellationToken cancellationToken ) { var product = Product.Create( - cmd.Name, - cmd.Description, - CategoryId.Create(cmd.CategoryId), - cmd.Sku, - Price.CreateNew(cmd.UnitPrice, cmd.Currency), - cmd.Discount, - cmd.Brand, - cmd.Barcode, - cmd.Tags + name: cmd.Name, + shortDescription: cmd.ShortDescription, + description: cmd.Description, + categoryId: CategoryId.Create(cmd.CategoryId), + sku: cmd.Sku, + unitPrice: Price.CreateNew(cmd.UnitPrice, cmd.Currency), + discount: cmd.Discount, + brand: cmd.Brand, + barcode: cmd.Barcode, + colors: cmd.Colors, + sizes: cmd.Sizes, + tags: cmd.Tags ); await _productRepository.AddAsync(product); diff --git a/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommand.cs b/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommand.cs index ee60ef8..6befef3 100644 --- a/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommand.cs +++ b/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommand.cs @@ -11,6 +11,7 @@ public record UpdateProductCommand( Guid UserId, Guid ProductId, string Name, + string ShortDescription, string Description, Guid CategoryId, decimal UnitPrice, @@ -18,6 +19,8 @@ public record UpdateProductCommand( decimal Discount, string Sku, string Brand, + string Colors, + string Sizes, string Tags, string Barcode, IList? SpecificationIds diff --git a/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs b/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs index bf7fce0..7de44fa 100644 --- a/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs +++ b/src/Shopizy.Application/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs @@ -26,15 +26,18 @@ CancellationToken cancellationToken } product.Update( - cmd.Name, - cmd.Description, - CategoryId.Create(cmd.CategoryId), - cmd.Sku, - Price.CreateNew(cmd.UnitPrice, cmd.Currency), - cmd.Discount, - cmd.Brand, - cmd.Barcode, - cmd.Tags + name: cmd.Name, + shortDescription: cmd.ShortDescription, + description: cmd.Description, + categoryId: CategoryId.Create(cmd.CategoryId), + sku: cmd.Sku, + unitPrice: Price.CreateNew(cmd.UnitPrice, cmd.Currency), + discount: cmd.Discount, + brand: cmd.Brand, + barcode: cmd.Barcode, + colors: cmd.Colors, + sizes: cmd.Sizes, + tags: cmd.Tags ); _productRepository.Update(product); diff --git a/src/Shopizy.Application/Products/Queries/GetProducts/GetProductsQuery.cs b/src/Shopizy.Application/Products/Queries/GetProducts/GetProductsQuery.cs index e5d3655..40d9a4a 100644 --- a/src/Shopizy.Application/Products/Queries/GetProducts/GetProductsQuery.cs +++ b/src/Shopizy.Application/Products/Queries/GetProducts/GetProductsQuery.cs @@ -7,7 +7,7 @@ namespace Shopizy.Application.Products.Queries.GetProducts; public record GetProductsQuery( string? Name, IList? CategoryIds, - double? AverageRating, + decimal? AverageRating, int PageNumber, int PageSize ) : IRequest>>; diff --git a/src/Shopizy.Contracts/Cart/AddProductToCartRequest.cs b/src/Shopizy.Contracts/Cart/AddProductToCartRequest.cs index 7f8e002..09336f5 100644 --- a/src/Shopizy.Contracts/Cart/AddProductToCartRequest.cs +++ b/src/Shopizy.Contracts/Cart/AddProductToCartRequest.cs @@ -1,3 +1,3 @@ namespace Shopizy.Contracts.Cart; -public record AddProductToCartRequest(Guid ProductId); +public record AddProductToCartRequest(Guid ProductId, string Color, string Size); diff --git a/src/Shopizy.Contracts/Cart/CartResponse.cs b/src/Shopizy.Contracts/Cart/CartResponse.cs index d969777..01bd09a 100644 --- a/src/Shopizy.Contracts/Cart/CartResponse.cs +++ b/src/Shopizy.Contracts/Cart/CartResponse.cs @@ -5,17 +5,19 @@ public record CartResponse( Guid UserId, DateTime CreatedOn, DateTime ModifiedOn, - IList LineItems + IList CartItems ); -public record LineItemResponse( - Guid LineItemId, +public record CartItemResponse( + Guid CartItemId, Guid ProductId, + string Color, + string Size, int Quantity, - LineProductResponse Product + CartProductResponse Product ); -public record LineProductResponse( +public record CartProductResponse( string Name, string Description, string Price, diff --git a/src/Shopizy.Contracts/Cart/CreateCartWithFirstProductRequest.cs b/src/Shopizy.Contracts/Cart/CreateCartWithFirstProductRequest.cs index f8e1f63..9626ab8 100644 --- a/src/Shopizy.Contracts/Cart/CreateCartWithFirstProductRequest.cs +++ b/src/Shopizy.Contracts/Cart/CreateCartWithFirstProductRequest.cs @@ -1,3 +1,3 @@ namespace Shopizy.Contracts.Cart; -public record CreateCartWithFirstProductRequest(Guid ProductId); +public record CreateCartWithFirstProductRequest(Guid ProductId, string Color, string Size); diff --git a/src/Shopizy.Contracts/Order/CreateOrderRequest.cs b/src/Shopizy.Contracts/Order/CreateOrderRequest.cs index 2bac78a..8fb7f77 100644 --- a/src/Shopizy.Contracts/Order/CreateOrderRequest.cs +++ b/src/Shopizy.Contracts/Order/CreateOrderRequest.cs @@ -2,6 +2,7 @@ namespace Shopizy.Contracts.Order; public record CreateOrderRequest( string PromoCode, + int DeliveryMethod, Price DeliveryCharge, IList OrderItems, Address ShippingAddress @@ -9,6 +10,6 @@ Address ShippingAddress public record Price(decimal Amount, string Currency); -public record OrderItemRequest(Guid ProductId, int Quantity); +public record OrderItemRequest(Guid ProductId, string Color, string Size, int Quantity); public record Address(string Street, string City, string State, string Country, string ZipCode); diff --git a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs index 06a856b..4ee3843 100644 --- a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs +++ b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs @@ -5,6 +5,7 @@ public record CardNotPresentSaleRequest( double Amount, string Currency, string PaymentMethod, + string PaymentMethodId, string CardName, string CardExpiryMonth, string CardExpiryYear, diff --git a/src/Shopizy.Contracts/Product/CreateProductRequest.cs b/src/Shopizy.Contracts/Product/CreateProductRequest.cs index 9ba0179..ca4f6c8 100644 --- a/src/Shopizy.Contracts/Product/CreateProductRequest.cs +++ b/src/Shopizy.Contracts/Product/CreateProductRequest.cs @@ -2,6 +2,7 @@ namespace Shopizy.Contracts.Product; public record CreateProductRequest( string Name, + string ShortDescription, string Description, Guid CategoryId, decimal UnitPrice, @@ -9,6 +10,8 @@ public record CreateProductRequest( decimal Discount, string Sku, string Brand, + string Colors, + string Sizes, string Tags, string Barcode, IList? SpecificationIds diff --git a/src/Shopizy.Contracts/Product/ProductResponse.cs b/src/Shopizy.Contracts/Product/ProductResponse.cs index 8c477e2..f3e1787 100644 --- a/src/Shopizy.Contracts/Product/ProductResponse.cs +++ b/src/Shopizy.Contracts/Product/ProductResponse.cs @@ -3,17 +3,52 @@ namespace Shopizy.Contracts.Product; public record ProductResponse( Guid ProductId, string Name, + string ShortDescription, string Description, Guid CategoryId, string Price, decimal Discount, - string Sku, string Brand, + string Sizes, + string Colors, string Tags, string Barcode, int StockQuantity, - IList? SpecificationIds, + AverageRating AverageRating, IList ProductImages ); +public record ProductDetailResponse( + Guid ProductId, + string Name, + string ShortDescription, + string Description, + Guid CategoryId, + string Price, + decimal Discount, + string Sku, + string Brand, + string Sizes, + string Colors, + string Tags, + string Barcode, + int StockQuantity, + AverageRating AverageRating, + int Favourites, + IList? Specifications, + IList ProductImages, + IList ProductReviews +); + public record ProductImageResponse(Guid ProductImageId, string ImageUrl, int Seq, string PublicId); + +public record ProductReviewResponse( + Guid ProductReviewId, + string Reviewer, + string? ReviewerImageUrl, + string Comment, + decimal Rating, + DateTime CreatedOn +); + +public record AverageRating(decimal Value, int NumRatings); diff --git a/src/Shopizy.Contracts/Product/ProductsCriteriaRequest.cs b/src/Shopizy.Contracts/Product/ProductsCriteriaRequest.cs index 279b1e4..9c9009a 100644 --- a/src/Shopizy.Contracts/Product/ProductsCriteriaRequest.cs +++ b/src/Shopizy.Contracts/Product/ProductsCriteriaRequest.cs @@ -3,7 +3,7 @@ namespace shopizy.Contracts.Product; public record ProductsCriteriaRequest( string? Name, IList? CategoryIds, - double? AverageRating, + decimal? AverageRating, int PageNumber = 1, int PageSize = 10 ); diff --git a/src/Shopizy.Contracts/Product/UpdateProductRequest.cs b/src/Shopizy.Contracts/Product/UpdateProductRequest.cs index 60e5143..43a6cc0 100644 --- a/src/Shopizy.Contracts/Product/UpdateProductRequest.cs +++ b/src/Shopizy.Contracts/Product/UpdateProductRequest.cs @@ -2,6 +2,7 @@ namespace Shopizy.Contracts.Product; public record UpdateProductRequest( string Name, + string ShortDescription, string Description, Guid CategoryId, decimal UnitPrice, @@ -9,6 +10,8 @@ public record UpdateProductRequest( decimal Discount, string Sku, string Brand, + string Colors, + string Sizes, string Tags, string Barcode, IList? SpecificationIds diff --git a/src/Shopizy.Domain/Carts/Cart.cs b/src/Shopizy.Domain/Carts/Cart.cs index c5d3ff6..cd63036 100644 --- a/src/Shopizy.Domain/Carts/Cart.cs +++ b/src/Shopizy.Domain/Carts/Cart.cs @@ -8,30 +8,32 @@ namespace Shopizy.Domain.Carts; public sealed class Cart : AggregateRoot { - private readonly List _lineItems = []; + private readonly List _cartItems = []; public UserId UserId { get; } public DateTime CreatedOn { get; private set; } public DateTime? ModifiedOn { get; private set; } - public IReadOnlyList LineItems => _lineItems.AsReadOnly(); + public IReadOnlyList CartItems => _cartItems.AsReadOnly(); public static Cart Create(UserId userId) { return new Cart(CartId.CreateUnique(), userId); } - public void AddLineItem(LineItem lineItem) + public void AddLineItem(CartItem lineItem) { - _lineItems.Add(lineItem); + _cartItems.Add(lineItem); } - public void RemoveLineItem(LineItem lineItem) + public void RemoveLineItem(CartItem lineItem) { - _lineItems.Remove(lineItem); + _cartItems.Remove(lineItem); + ModifiedOn = DateTime.UtcNow; } public void UpdateLineItem(ProductId productId, int quantity) { - _lineItems.Find(li => li.ProductId == productId)?.UpdateQuantity(quantity); + _cartItems.Find(li => li.ProductId == productId)?.UpdateQuantity(quantity); + ModifiedOn = DateTime.UtcNow; } private Cart(CartId cartId, UserId userId) diff --git a/src/Shopizy.Domain/Carts/Entities/LineItem.cs b/src/Shopizy.Domain/Carts/Entities/CartItem.cs similarity index 51% rename from src/Shopizy.Domain/Carts/Entities/LineItem.cs rename to src/Shopizy.Domain/Carts/Entities/CartItem.cs index 26a7693..cd579d5 100644 --- a/src/Shopizy.Domain/Carts/Entities/LineItem.cs +++ b/src/Shopizy.Domain/Carts/Entities/CartItem.cs @@ -5,15 +5,17 @@ namespace Shopizy.Domain.Carts.Entities; -public sealed class LineItem : Entity +public sealed class CartItem : Entity { public Product Product { get; private set; } = null!; public ProductId ProductId { get; private set; } + public string Color { get; private set; } + public string Size { get; private set; } public int Quantity { get; private set; } - public static LineItem Create(ProductId productId) + public static CartItem Create(ProductId productId, string color, string size) { - return new LineItem(LineItemId.CreateUnique(), productId); + return new CartItem(CartItemId.CreateUnique(), productId, color, size); } public void UpdateQuantity(int quantity) @@ -21,11 +23,14 @@ public void UpdateQuantity(int quantity) Quantity = quantity; } - private LineItem(LineItemId lineItemId, ProductId productId) : base(lineItemId) + private CartItem(CartItemId CartItemId, ProductId productId, string color, string size) + : base(CartItemId) { ProductId = productId; + Color = color; + Size = size; Quantity = 1; } - private LineItem() { } + private CartItem() { } } diff --git a/src/Shopizy.Domain/Carts/ValueObjects/LineItemId.cs b/src/Shopizy.Domain/Carts/ValueObjects/CartItemId.cs similarity index 67% rename from src/Shopizy.Domain/Carts/ValueObjects/LineItemId.cs rename to src/Shopizy.Domain/Carts/ValueObjects/CartItemId.cs index 455311b..5538029 100644 --- a/src/Shopizy.Domain/Carts/ValueObjects/LineItemId.cs +++ b/src/Shopizy.Domain/Carts/ValueObjects/CartItemId.cs @@ -2,21 +2,21 @@ namespace Shopizy.Domain.Carts.ValueObjects; -public sealed class LineItemId : AggregateRootId +public sealed class CartItemId : AggregateRootId { public override Guid Value { get; protected set; } - private LineItemId(Guid value) + private CartItemId(Guid value) { Value = value; } - public static LineItemId CreateUnique() + public static CartItemId CreateUnique() { return new(Guid.NewGuid()); } - public static LineItemId Create(Guid value) + public static CartItemId Create(Guid value) { return new(value); } diff --git a/src/Shopizy.Domain/Common/ValueObjects/AverageRating.cs b/src/Shopizy.Domain/Common/ValueObjects/AverageRating.cs index 36ce68a..5d1c56c 100644 --- a/src/Shopizy.Domain/Common/ValueObjects/AverageRating.cs +++ b/src/Shopizy.Domain/Common/ValueObjects/AverageRating.cs @@ -4,16 +4,16 @@ namespace Shopizy.Domain.Common.ValueObjects; public sealed class AverageRating : ValueObject { - private AverageRating(double value, int numRatings) + private AverageRating(decimal value, int numRatings) { Value = value; NumRatings = numRatings; } - public double Value { get; private set; } + public decimal Value { get; private set; } public int NumRatings { get; private set; } - public static AverageRating CreateNew(double rating = 0, int numRatings = 0) + public static AverageRating CreateNew(decimal rating = 0, int numRatings = 0) { return new AverageRating(rating, numRatings); } diff --git a/src/Shopizy.Domain/Common/ValueObjects/Ratings.cs b/src/Shopizy.Domain/Common/ValueObjects/Ratings.cs index 6a9a760..d0cf61e 100644 --- a/src/Shopizy.Domain/Common/ValueObjects/Ratings.cs +++ b/src/Shopizy.Domain/Common/ValueObjects/Ratings.cs @@ -4,14 +4,14 @@ namespace Shopizy.Domain.Common.ValueObjects; public sealed class Rating : ValueObject { - public double Value { get; private set; } + public decimal Value { get; private set; } - private Rating(double value) + private Rating(decimal value) { Value = value; } - public static Rating CreateNew(double value) + public static Rating CreateNew(decimal value) { return new(value); } diff --git a/src/Shopizy.Domain/Orders/Entities/OrderItem.cs b/src/Shopizy.Domain/Orders/Entities/OrderItem.cs index da54a22..8b2e629 100644 --- a/src/Shopizy.Domain/Orders/Entities/OrderItem.cs +++ b/src/Shopizy.Domain/Orders/Entities/OrderItem.cs @@ -9,6 +9,8 @@ public sealed class OrderItem : Entity public string Name { get; private set; } public string PictureUrl { get; private set; } public Price UnitPrice { get; private set; } + public string Color { get; private set; } + public string Size { get; private set; } public int Quantity { get; private set; } public decimal Discount { get; private set; } @@ -17,6 +19,8 @@ public static OrderItem Create( string pictureUrl, Price unitPrice, int quantity, + string color, + string size, decimal? discount ) { @@ -26,6 +30,8 @@ public static OrderItem Create( pictureUrl, unitPrice, quantity, + color, + size, discount ); } @@ -36,13 +42,18 @@ private OrderItem( string pictureUrl, Price unitPrice, int quantity, + string color, + string size, decimal? discount - ) : base(orderItemId) + ) + : base(orderItemId) { Name = name; PictureUrl = pictureUrl; UnitPrice = unitPrice; Quantity = quantity; + Color = color; + Size = size; Discount = discount ?? 0; } @@ -55,6 +66,6 @@ public Price TotalPrice() public Price TotalDiscount() { - return Price.CreateNew((Discount / 100) * Quantity, UnitPrice.Currency); + return Price.CreateNew(UnitPrice.Amount * (Discount / 100) * Quantity, UnitPrice.Currency); } } diff --git a/src/Shopizy.Domain/Orders/Enums/OderStatus.cs b/src/Shopizy.Domain/Orders/Enums/OderStatus.cs index ecfa93a..b2b118c 100644 --- a/src/Shopizy.Domain/Orders/Enums/OderStatus.cs +++ b/src/Shopizy.Domain/Orders/Enums/OderStatus.cs @@ -7,5 +7,13 @@ public enum OrderStatus StockConfirmed = 3, Paid = 4, Shipped = 5, - Cancelled = 6 + Cancelled = 6, +} + +public enum DeliveryMethods +{ + Free = 0, + Standard = 1, + Express = 2, + Premium = 3, } diff --git a/src/Shopizy.Domain/Orders/Order.cs b/src/Shopizy.Domain/Orders/Order.cs index 275cb2c..90270c1 100644 --- a/src/Shopizy.Domain/Orders/Order.cs +++ b/src/Shopizy.Domain/Orders/Order.cs @@ -12,6 +12,7 @@ public sealed class Order : AggregateRoot { private readonly IList _orderItems = []; public UserId UserId { get; private set; } + public DeliveryMethods DeliveryMethod { get; private set; } public Price DeliveryCharge { get; private set; } public OrderStatus OrderStatus { get; private set; } public string? CancellationReason { get; private set; } @@ -25,6 +26,7 @@ public sealed class Order : AggregateRoot public static Order Create( UserId userId, string promoCode, + int deliveryMethod, Price deliveryCharge, Address shippingAddress, IList orderItems @@ -34,6 +36,7 @@ IList orderItems OrderId.CreateUnique(), userId, promoCode, + deliveryMethod, deliveryCharge, shippingAddress, orderItems @@ -44,6 +47,7 @@ private Order( OrderId orderId, UserId userId, string promoCode, + int deliveryMethod, Price deliveryCharge, Address shippingAddress, IList orderItems @@ -53,6 +57,7 @@ IList orderItems UserId = userId; OrderStatus = OrderStatus.Submitted; PromoCode = promoCode; + DeliveryMethod = (DeliveryMethods)deliveryMethod; DeliveryCharge = deliveryCharge; ShippingAddress = shippingAddress; _orderItems = orderItems; diff --git a/src/Shopizy.Domain/Payments/Payment.cs b/src/Shopizy.Domain/Payments/Payment.cs index c00fe4a..41a5922 100644 --- a/src/Shopizy.Domain/Payments/Payment.cs +++ b/src/Shopizy.Domain/Payments/Payment.cs @@ -16,6 +16,7 @@ public sealed class Payment : Entity public User User { get; set; } = null!; public UserId UserId { get; set; } public string PaymentMethod { get; private set; } + public string PaymentMethodId { get; private set; } public string TransactionId { get; private set; } public PaymentStatus PaymentStatus { get; private set; } public Price Total { get; private set; } @@ -27,6 +28,7 @@ public static Payment Create( UserId userId, OrderId orderId, string paymentMethod, + string paymentMethodId, string transactionId, PaymentStatus paymentStatus, Price total, @@ -38,6 +40,7 @@ Address billingAddress userId, orderId, paymentMethod, + paymentMethodId, transactionId, paymentStatus, total, @@ -50,6 +53,7 @@ private Payment( UserId userId, OrderId orderId, string paymentMethod, + string paymentMethodId, string transactionId, PaymentStatus paymentStatus, Price total, @@ -60,6 +64,7 @@ Address billingAddress UserId = userId; OrderId = orderId; PaymentMethod = paymentMethod; + PaymentMethodId = paymentMethodId; TransactionId = transactionId; PaymentStatus = paymentStatus; Total = total; @@ -74,4 +79,10 @@ public void UpdatePaymentStatus(PaymentStatus status) PaymentStatus = status; ModifiedOn = DateTime.UtcNow; } + + public void UpdateTransactionId(string transactionId) + { + TransactionId = transactionId; + ModifiedOn = DateTime.UtcNow; + } } diff --git a/src/Shopizy.Domain/ProductReviews/ProductReview.cs b/src/Shopizy.Domain/ProductReviews/ProductReview.cs index 74b31ba..94cf793 100644 --- a/src/Shopizy.Domain/ProductReviews/ProductReview.cs +++ b/src/Shopizy.Domain/ProductReviews/ProductReview.cs @@ -2,6 +2,7 @@ using Shopizy.Domain.Common.ValueObjects; using Shopizy.Domain.ProductReviews.ValueObjects; using Shopizy.Domain.Products.ValueObjects; +using Shopizy.Domain.Users; using Shopizy.Domain.Users.ValueObjects; namespace Shopizy.Domain.ProductReviews; @@ -9,6 +10,7 @@ namespace Shopizy.Domain.ProductReviews; public sealed class ProductReview : AggregateRoot { public UserId UserId { get; set; } + public User User { get; set; } public ProductId ProductId { get; set; } public Rating Rating { get; set; } public string Comment { get; set; } @@ -37,7 +39,8 @@ private ProductReview( ProductId productId, Rating rating, string comment - ) : base(productReviewId) + ) + : base(productReviewId) { UserId = userId; ProductId = productId; diff --git a/src/Shopizy.Domain/Products/Product.cs b/src/Shopizy.Domain/Products/Product.cs index 0479217..85b470b 100644 --- a/src/Shopizy.Domain/Products/Product.cs +++ b/src/Shopizy.Domain/Products/Product.cs @@ -12,6 +12,7 @@ public sealed class Product : AggregateRoot private readonly List _productImages = []; private readonly List _productReviews = []; public string Name { get; private set; } + public string ShortDescription { get; private set; } public string Description { get; private set; } public CategoryId CategoryId { get; private set; } public string SKU { get; private set; } @@ -19,6 +20,9 @@ public sealed class Product : AggregateRoot public Price UnitPrice { get; private set; } public decimal? Discount { get; private set; } public string Brand { get; private set; } + public string Colors { get; private set; } + public string Sizes { get; private set; } + public int Favourites { get; private set; } public string Barcode { get; private set; } public string Tags { get; private set; } public AverageRating AverageRating { get; private set; } @@ -30,6 +34,7 @@ public sealed class Product : AggregateRoot public static Product Create( string name, + string shortDescription, string description, CategoryId categoryId, string sku, @@ -37,12 +42,15 @@ public static Product Create( decimal? discount, string brand, string barcode, + string colors, + string sizes, string tags ) { return new Product( ProductId.CreateUnique(), name, + shortDescription, description, categoryId, sku, @@ -51,6 +59,8 @@ string tags discount, brand, barcode, + colors, + sizes, tags, AverageRating.CreateNew(0) ); @@ -58,6 +68,7 @@ string tags public void Update( string name, + string shortDescription, string description, CategoryId categoryId, string sku, @@ -65,10 +76,13 @@ public void Update( decimal? discount, string brand, string barcode, + string colors, + string sizes, string tags ) { Name = name; + ShortDescription = shortDescription; Description = description; CategoryId = categoryId; SKU = sku; @@ -76,11 +90,13 @@ string tags Discount = discount; Brand = brand; Barcode = barcode; + Colors = colors; + Sizes = sizes; Tags = tags; ModifiedOn = DateTime.UtcNow; } - public void AddProductImages(List productImages) + public void AddProductImages(IList productImages) { _productImages.AddRange(productImages); } @@ -95,9 +111,15 @@ public void RemoveProductImage(ProductImage productImage) _productImages.Remove(productImage); } + public void UpdateFavourite() + { + Favourites += 1; + } + private Product( ProductId productId, string name, + string shortDescription, string description, CategoryId categoryId, string sku, @@ -106,12 +128,15 @@ private Product( decimal? discount, string brand, string barcode, + string colors, + string sizes, string tags, AverageRating averageRating ) : base(productId) { Name = name; + ShortDescription = shortDescription; Description = description; CategoryId = categoryId; SKU = sku; @@ -120,6 +145,8 @@ AverageRating averageRating Discount = discount; Brand = brand; Barcode = barcode; + Colors = colors; + Sizes = sizes; Tags = tags; AverageRating = averageRating; CreatedOn = DateTime.UtcNow; diff --git a/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs b/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs index 1ee4b72..f98ce0b 100644 --- a/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs +++ b/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs @@ -13,7 +13,7 @@ public sealed class CartConfigurations : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { ConfigureCartsTable(builder); - ConfigureCartProductIdsTable(builder); + ConfigureCartItemsTable(builder); } private static void ConfigureCartsTable(EntityTypeBuilder builder) @@ -36,27 +36,27 @@ private static void ConfigureCartsTable(EntityTypeBuilder builder) .HasConversion(id => id.Value, value => UserId.Create(value)); } - private static void ConfigureCartProductIdsTable(EntityTypeBuilder builder) + private static void ConfigureCartItemsTable(EntityTypeBuilder builder) { builder.OwnsMany( - m => m.LineItems, - ltmb => + m => m.CartItems, + ci => { - ltmb.ToTable("LineItems"); - ltmb.WithOwner().HasForeignKey("CartId"); - ltmb.HasKey(nameof(LineItem.Id), "CartId"); - ltmb.HasIndex("CartId", "ProductId").IsUnique(); + ci.ToTable("CartItems"); + ci.WithOwner().HasForeignKey("CartId"); + ci.HasKey(nameof(CartItem.Id), "CartId"); + ci.HasIndex("CartId", "ProductId").IsUnique(); - ltmb.Property(li => li.Id) + ci.Property(li => li.Id) .ValueGeneratedNever() - .HasConversion(id => id.Value, value => LineItemId.Create(value)); + .HasConversion(id => id.Value, value => CartItemId.Create(value)); - ltmb.Property(li => li.ProductId) + ci.Property(li => li.ProductId) .ValueGeneratedNever() .HasConversion(id => id.Value, value => ProductId.Create(value)); - ltmb.Navigation(li => li.Product).UsePropertyAccessMode(PropertyAccessMode.Field); + ci.Navigation(li => li.Product).UsePropertyAccessMode(PropertyAccessMode.Field); } ); - builder.Navigation(p => p.LineItems).UsePropertyAccessMode(PropertyAccessMode.Field); + builder.Navigation(p => p.CartItems).UsePropertyAccessMode(PropertyAccessMode.Field); } } diff --git a/src/Shopizy.Infrastructure/Carts/Persistence/CartRepository.cs b/src/Shopizy.Infrastructure/Carts/Persistence/CartRepository.cs index 9309266..349db37 100644 --- a/src/Shopizy.Infrastructure/Carts/Persistence/CartRepository.cs +++ b/src/Shopizy.Infrastructure/Carts/Persistence/CartRepository.cs @@ -24,7 +24,7 @@ public Task> GetCartsAsync() public Task GetCartByUserIdAsync(UserId id) { return _dbContext - .Carts.Include(c => c.LineItems) + .Carts.Include(c => c.CartItems) .ThenInclude(li => li.Product) .FirstOrDefaultAsync(c => c.UserId == id); } diff --git a/src/Shopizy.Infrastructure/Common/Middleware/EventualConsistencyMiddleware.cs b/src/Shopizy.Infrastructure/Common/Middleware/EventualConsistencyMiddleware.cs index b4bb45f..77d04a4 100644 --- a/src/Shopizy.Infrastructure/Common/Middleware/EventualConsistencyMiddleware.cs +++ b/src/Shopizy.Infrastructure/Common/Middleware/EventualConsistencyMiddleware.cs @@ -11,12 +11,16 @@ public class EventualConsistencyMiddleware(RequestDelegate _next) public async Task InvokeAsync(HttpContext context, IPublisher publisher, AppDbContext dbContext) { - Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = await dbContext.Database.BeginTransactionAsync(); + Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = + await dbContext.Database.BeginTransactionAsync(); context.Response.OnCompleted(async () => { try { - if (context.Items.TryGetValue(DomainEventsKey, out object? value) && value is Queue domainEvent) + if ( + context.Items.TryGetValue(DomainEventsKey, out object? value) + && value is Queue domainEvent + ) { while (domainEvent.TryDequeue(out IDomainEvent? nextEvent)) { diff --git a/src/Shopizy.Infrastructure/Common/SeedData/ProductImages.json b/src/Shopizy.Infrastructure/Common/SeedData/ProductImages.json index 06ce685..e1df8cd 100644 --- a/src/Shopizy.Infrastructure/Common/SeedData/ProductImages.json +++ b/src/Shopizy.Infrastructure/Common/SeedData/ProductImages.json @@ -68,5 +68,12 @@ "imageUrl": "https://res.cloudinary.com/akazad13/image/upload/v1729420812/shopizy/ojtymvp04czf75leclg2.jpg", "seq": 0, "publicId": "ojtymvp04czf75leclg2" + }, + { + "id": "A8A70F86-7987-4BB4-A344-1B2133DCC609", + "productId": "096A127A-65AF-4C0C-A294-2E1990136711", + "imageUrl": "https://res.cloudinary.com/akazad13/image/upload/v1733579506/shopizy/men2_2_d4dn3p.jpg", + "seq": 1, + "publicId": "men2_2_d4dn3p" } ] diff --git a/src/Shopizy.Infrastructure/Common/SeedData/Products.json b/src/Shopizy.Infrastructure/Common/SeedData/Products.json index f3b72e6..3743211 100644 --- a/src/Shopizy.Infrastructure/Common/SeedData/Products.json +++ b/src/Shopizy.Infrastructure/Common/SeedData/Products.json @@ -2,6 +2,7 @@ { "id": "571D3D66-18F2-4DA8-A57B-D06D4D1C9EEF", "name": "Black Rockstar", + "ShortDescription": "High quality black t-shirt", "description": "High quality black t-shirt", "categoryId": "106F4F94-5E70-4340-B23E-462AF5FC7BFC", "sku": "MC-10000000T", @@ -18,6 +19,7 @@ { "id": "096A127A-65AF-4C0C-A294-2E1990136711", "name": "Premium Purple", + "ShortDescription": "High quality purple t-shirt", "description": "High quality purple t-shirt", "categoryId": "106F4F94-5E70-4340-B23E-462AF5FC7BFC", "sku": "MC-10000002T", @@ -34,6 +36,7 @@ { "id": "8419FD69-61A5-4D9D-A289-1E377CC17082", "name": "Red Dragon", + "ShortDescription": "Premium quality red sweater", "description": "Premium quality red sweater", "categoryId": "632DFDA9-CC2E-487B-8C88-608005F124E2", "sku": "MC-10000000S", @@ -50,6 +53,7 @@ { "id": "CBB8A3A9-03DB-40E4-A89B-9C91D21B3934", "name": "Ash life", + "ShortDescription": "High quality t-shirt", "description": "High quality t-shirt", "categoryId": "106F4F94-5E70-4340-B23E-462AF5FC7BFC", "sku": "MC-10000004T", @@ -66,6 +70,7 @@ { "id": "96A1503F-B7EA-4C12-8946-89035399F45E", "name": "Red Star", + "ShortDescription": "High quality t-shirt for men", "description": "High quality t-shirt for men", "categoryId": "106F4F94-5E70-4340-B23E-462AF5FC7BFC", "sku": "MC-10000006T", @@ -82,6 +87,7 @@ { "id": "E02500EA-2957-4601-A051-951CB9957CBE", "name": "Black Stone", + "ShortDescription": "Premium quality t-shirt for women", "description": "Premium quality t-shirt for women", "categoryId": "A805A418-2D53-4430-9968-D031C4F39FD4", "sku": "FC-10000000T", @@ -98,6 +104,7 @@ { "id": "A34BA283-62A1-43C6-8FB0-748DBDE8BA7D", "name": "Elegant peach", + "ShortDescription": "High quality women top", "description": "High quality women top", "categoryId": "BA5FE17F-8977-4034-BEC3-227AA99502CC", "sku": "FC-10000002T", @@ -114,6 +121,7 @@ { "id": "D7C96816-1656-4DBC-B709-0F626807347F", "name": "Pure White", + "ShortDescription": "High quality women t-shirt", "description": "High quality women t-shirt", "categoryId": "A805A418-2D53-4430-9968-D031C4F39FD4", "sku": "FC-10000004T", @@ -130,6 +138,7 @@ { "id": "DF22BA6B-A15B-4125-AF29-07E242AF6F2D", "name": "Natural Green", + "ShortDescription": "High quality t-shirt", "description": "High quality t-shirt", "categoryId": "A805A418-2D53-4430-9968-D031C4F39FD4", "sku": "FC-10000006T", @@ -146,6 +155,7 @@ { "id": "9EB9982F-E672-4184-8399-044277EEF754", "name": "New Way", + "ShortDescription": "Premium quality top", "description": "Premium quality top", "categoryId": "BA5FE17F-8977-4034-BEC3-227AA99502CC", "sku": "FC-10000008T", diff --git a/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL b/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL index 2f84b21..292b5ba 100644 --- a/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL +++ b/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL @@ -26,17 +26,17 @@ INSERT INTO [Categories] ([id], [name], [parentId]) VALUES -INSERT INTO [Products] ([id], [name], [description], [categoryId], [sku], [stockQuantity], [unitPrice_Amount], [unitPrice_Currency], [discount], [brand], [barcode], [tags], [averageRating_Value], [averageRating_NumRatings], [CreatedOn]) VALUES - ('571D3D66-18F2-4DA8-A57B-D06D4D1C9EEF', 'Black Rockstar', 'High quality black t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000000T', '10', '20', '0', '10', 'Zara', 'MC-10000000T', 't-shirt, zara, men, new', '4.8', '5', GETDATE()), - ('096A127A-65AF-4C0C-A294-2E1990136711', 'Premium Purple', 'High quality purple t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000002T', '5', '22', '0', '10', 'ck', 'MC-10000002T', 't-shirt, ck, men, new, premium', '4.9', '7', GETDATE()), - ('8419FD69-61A5-4D9D-A289-1E377CC17082', 'Red Dragon', 'Premium quality red sweater', '632DFDA9-CC2E-487B-8C88-608005F124E2', 'MC-10000000S', '20', '40', '0', '10', 'h&m', 'MC-10000000S', 'sweater, h&m, men, hot, premium', '4.7', '15', GETDATE()), - ('CBB8A3A9-03DB-40E4-A89B-9C91D21B3934', 'Ash life', 'High quality t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000004T', '15', '25', '0', '10', 'ck', 'MC-10000004T', 't-shirt, ck, men, new, premium', '4.7', '5', GETDATE()), - ('96A1503F-B7EA-4C12-8946-89035399F45E', 'Red Star', 'High quality t-shirt for men', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000006T', '30', '20', '0', '10', 'Boss', 'MC-10000006T', 't-shirt, boss, men', '4.6', '10', GETDATE()), - ('E02500EA-2957-4601-A051-951CB9957CBE', 'Black Stone', 'Premium quality t-shirt for women', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000000T', '30', '40', '0', '10', 'Adidas', 'FC-10000000T', 't-shirt, adidas, new, premium', '4.9', '15', GETDATE()), - ('A34BA283-62A1-43C6-8FB0-748DBDE8BA7D', 'Elegant peach', 'High quality women top', 'BA5FE17F-8977-4034-BEC3-227AA99502CC', 'FC-10000002T', '10', '20', '0', '10', 'h&m', 'FC-10000002T', 'top, h&m, new', '4.7', '15', GETDATE()), - ('D7C96816-1656-4DBC-B709-0F626807347F', 'Pure White', 'High quality women t-shirt', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000004T', '20', '20', '0', '10', 'ck', 'FC-10000004T', 't-shirt, ck, new', '4.7', '5', GETDATE()), - ('DF22BA6B-A15B-4125-AF29-07E242AF6F2D', 'Natural Green', 'High quality t-shirt', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000006T', '20', '25', '0', '5', 'Zara', 'FC-10000006T', 't-shirt, zara', '4.7', '5', GETDATE()), - ('9EB9982F-E672-4184-8399-044277EEF754', 'New Way', 'Premium quality top', 'BA5FE17F-8977-4034-BEC3-227AA99502CC', 'FC-10000008T', '30', '30', '0', '10', 'Nike', 'FC-10000008T', 'top, nike, new', '4.8', '20', GETDATE()); +INSERT INTO [Products] ([id], [name], [description], [ShortDescription], [categoryId], [sku], [stockQuantity], [unitPrice_Amount], [unitPrice_Currency], [discount], [brand], [barcode], [tags], [averageRating_Value], [averageRating_NumRatings], [CreatedOn], [Colors], [Sizes]) VALUES + ('571D3D66-18F2-4DA8-A57B-D06D4D1C9EEF', 'Black Rockstar', 'High quality black t-shirt', 'High quality black t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000000T', '10', '20', '0', '10', 'Zara', 'MC-10000000T', 't-shirt, zara, men, new', '4.8', '5', GETDATE(), 'Black,Gray,Orange,Pink', 'XS,S,M,L,XL,2XL'), + ('096A127A-65AF-4C0C-A294-2E1990136711', 'Premium Purple', 'High quality purple t-shirt', 'High quality purple t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000002T', '5', '22', '0', '10', 'ck', 'MC-10000002T', 't-shirt, ck, men, new, premium', '4.9', '7', GETDATE(), 'Black,Orange,Pink,White', 'XS,S,M,L,XL,2XL'), + ('8419FD69-61A5-4D9D-A289-1E377CC17082', 'Red Dragon', 'Premium quality red sweater', 'Premium quality red sweater', '632DFDA9-CC2E-487B-8C88-608005F124E2', 'MC-10000000S', '20', '40', '0', '10', 'h&m', 'MC-10000000S', 'sweater, h&m, men, hot, premium', '4.7', '15', GETDATE(), 'Red,Orange,Pink,White', 'XS,S,M,L,XL,2XL'), + ('CBB8A3A9-03DB-40E4-A89B-9C91D21B3934', 'Ash life', 'High quality t-shirt', 'High quality t-shirt', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000004T', '15', '25', '0', '10', 'ck', 'MC-10000004T', 't-shirt, ck, men, new, premium', '4.7', '5', GETDATE(), 'Black,Gray,Pink,White', 'XS,S,M,L,XL,2XL'), + ('96A1503F-B7EA-4C12-8946-89035399F45E', 'Red Star', 'High quality t-shirt for men', 'High quality t-shirt for men', '106F4F94-5E70-4340-B23E-462AF5FC7BFC', 'MC-10000006T', '30', '20', '0', '10', 'Boss', 'MC-10000006T', 't-shirt, boss, men', '4.6', '10', GETDATE(), 'Red,Black,Orange', 'XS,S,M,L,XL,2XL'), + ('E02500EA-2957-4601-A051-951CB9957CBE', 'Black Stone', 'Premium quality t-shirt for women', 'Premium quality t-shirt for women', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000000T', '30', '40', '0', '10', 'Adidas', 'FC-10000000T', 't-shirt, adidas, new, premium', '4.9', '15', GETDATE(), 'Black,Gray', 'XS,S,M,L,XL,2XL'), + ('A34BA283-62A1-43C6-8FB0-748DBDE8BA7D', 'Elegant peach', 'High quality women top', 'High quality women top', 'BA5FE17F-8977-4034-BEC3-227AA99502CC', 'FC-10000002T', '10', '20', '0', '10', 'h&m', 'FC-10000002T', 'top, h&m, new', '4.7', '15', GETDATE(), ',Orange,Pink,Peach', 'XS,S,M,L,XL,2XL'), + ('D7C96816-1656-4DBC-B709-0F626807347F', 'Pure White', 'High quality women t-shirt', 'High quality women t-shirt', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000004T', '20', '20', '0', '10', 'ck', 'FC-10000004T', 't-shirt, ck, new', '4.7', '5', GETDATE(), 'White', 'XS,S,M,L,XL,2XL'), + ('DF22BA6B-A15B-4125-AF29-07E242AF6F2D', 'Natural Green', 'High quality t-shirt', 'High quality t-shirt', 'A805A418-2D53-4430-9968-D031C4F39FD4', 'FC-10000006T', '20', '25', '0', '5', 'Zara', 'FC-10000006T', 't-shirt, zara', '4.6', '5', GETDATE(), 'Green', 'XS,S,M,L,XL,2XL'), + ('9EB9982F-E672-4184-8399-044277EEF754', 'New Way', 'Premium quality top', 'Premium quality top', 'BA5FE17F-8977-4034-BEC3-227AA99502CC', 'FC-10000008T', '30', '30', '0', '10', 'Nike', 'FC-10000008T', 'top, nike, new', '4.8', '20', GETDATE(), 'Black,Gray,Orange,Pink,White', 'XS,S,M,L,XL,2XL'); INSERT INTO [ProductImages] ([id], [productId], [imageUrl], [seq], [publicId]) VALUES @@ -49,4 +49,12 @@ INSERT INTO [ProductImages] ([id], [productId], [imageUrl], [seq], [publicId]) V ('44868001-5253-4A64-9CA3-039CD129ED05', 'A34BA283-62A1-43C6-8FB0-748DBDE8BA7D', 'https://res.cloudinary.com/akazad13/image/upload/v1729420811/shopizy/dhpe8da07epnawtqftof.jpg', '0', 'dhpe8da07epnawtqftof'), ('27332C6A-AFDC-4586-A83B-915707DE419F', 'D7C96816-1656-4DBC-B709-0F626807347F', 'https://res.cloudinary.com/akazad13/image/upload/v1729420811/shopizy/awjhedzbdirmtve1ukj6.jpg', '0', 'awjhedzbdirmtve1ukj6'), ('D3E23931-88E9-4E0E-9A61-77BFC02FA964', 'DF22BA6B-A15B-4125-AF29-07E242AF6F2D', 'https://res.cloudinary.com/akazad13/image/upload/v1729420811/shopizy/laeklvddgoewdct3hrly.jpg', '0', 'laeklvddgoewdct3hrly'), - ('D3E23931-88E9-4E0E-9A61-77BFC02FA964', '9EB9982F-E672-4184-8399-044277EEF754', 'https://res.cloudinary.com/akazad13/image/upload/v1729420812/shopizy/ojtymvp04czf75leclg2.jpg', '0', 'ojtymvp04czf75leclg2'); + ('D3E23931-88E9-4E0E-9A61-77BFC02FA964', '9EB9982F-E672-4184-8399-044277EEF754', 'https://res.cloudinary.com/akazad13/image/upload/v1729420812/shopizy/ojtymvp04czf75leclg2.jpg', '0', 'ojtymvp04czf75leclg2'), + ('A8A70F86-7987-4BB4-A344-1B2133DCC609', '096A127A-65AF-4C0C-A294-2E1990136711', 'https://res.cloudinary.com/akazad13/image/upload/v1733579506/shopizy/men2_2_d4dn3p.jpg', 1, 'men2_2_d4dn3p'); + +INSERT INTO ProductReviews(Id, UserId, ProductId, Rating_Value, Comment, CreatedOn) +VALUES('20AB2C8F-E880-45C3-8887-D0A0E2E0E763','E68D8E76-72A1-42ED-91D0-0ED0296D662E','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',5, 'Looks Awesome!', GETDATE()), +('C75B65D5-13D1-4FC9-A81E-19D7FB3B7784','67726B00-2647-4817-BFCB-3781EBD97E0F','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',5, 'Looks Awesome!', GETDATE()), +('41B53394-5014-45E6-9540-B7FA05782B6C','6EBA643C-D138-4D90-AA7C-6DBF935D8209','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',4, 'Good T-Shirt!', GETDATE()), +('F9CA8F97-2B91-4EAD-88D7-78496A92616A','88D9AF82-BEF4-4042-8E55-B01B89F23E68','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',5, 'Recommanded!', GETDATE()), +('7ED64773-2ED6-4BFB-BBA0-E267BA4A8208','E68D8E76-72A1-42ED-91D0-0ED0296D662E','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',4, 'Looks good', GETDATE()); diff --git a/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs b/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs index 48298e2..0477617 100644 --- a/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs +++ b/src/Shopizy.Infrastructure/ExternalServices/PaymentGateway/Stripe/StripeService.cs @@ -43,8 +43,8 @@ public async Task> CreateSaleAsync(CreateSaleRequest Customer = request.CustomerId, Amount = request.Amount, Currency = request.Currency, - // ConfirmationMethod = "manual", - // Confirm = request.CapturePayment, + // ConfirmationMethod = "manual", // if Confirm = false, then this will determine how a payment will be confirmed (From frontend/backend) + Confirm = request.CapturePayment, PaymentMethodTypes = request.PaymentMethodTypes, Metadata = request.Metadata, PaymentMethod = request.PaymentMethodId, @@ -54,6 +54,8 @@ public async Task> CreateSaleAsync(CreateSaleRequest { var response = await _paymentIntentService.CreateAsync(intentCreateOptions); + // var result = await _paymentIntentService.ConfirmAsync(response.Id); // if Confirm is false and we manaully confirm payment + return new CreateSaleResponse { ResponseStatusCode = (int)response.StripeResponse.StatusCode, diff --git a/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.Designer.cs new file mode 100644 index 0000000..47ee956 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.Designer.cs @@ -0,0 +1,758 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241202145010_UpdatePaymentTable")] + partial class UpdatePaymentTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("LineItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithOne() + .HasForeignKey("Shopizy.Domain.Payments.Payment", "OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithOne() + .HasForeignKey("Shopizy.Domain.Payments.Payment", "UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.cs b/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.cs new file mode 100644 index 0000000..d735f6f --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241202145010_UpdatePaymentTable.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdatePaymentTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "TransactionId", + table: "Payments", + type: "nvarchar(260)", + maxLength: 260, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(50)", + oldMaxLength: 50, + oldNullable: true); + + migrationBuilder.AddColumn( + name: "PaymentMethodId", + table: "Payments", + type: "nvarchar(260)", + maxLength: 260, + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PaymentMethodId", + table: "Payments"); + + migrationBuilder.AlterColumn( + name: "TransactionId", + table: "Payments", + type: "nvarchar(50)", + maxLength: 50, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(260)", + oldMaxLength: 260, + oldNullable: true); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.Designer.cs new file mode 100644 index 0000000..e703d89 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.Designer.cs @@ -0,0 +1,756 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241202154947_UpdatePaymentTableRef")] + partial class UpdatePaymentTableRef + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("LineItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("float(18)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.cs b/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.cs new file mode 100644 index 0000000..a0d48e9 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241202154947_UpdatePaymentTableRef.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdatePaymentTableRef : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Payments_OrderId", + table: "Payments"); + + migrationBuilder.DropIndex( + name: "IX_Payments_UserId", + table: "Payments"); + + migrationBuilder.CreateIndex( + name: "IX_Payments_OrderId", + table: "Payments", + column: "OrderId"); + + migrationBuilder.CreateIndex( + name: "IX_Payments_UserId", + table: "Payments", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Payments_OrderId", + table: "Payments"); + + migrationBuilder.DropIndex( + name: "IX_Payments_UserId", + table: "Payments"); + + migrationBuilder.CreateIndex( + name: "IX_Payments_OrderId", + table: "Payments", + column: "OrderId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Payments_UserId", + table: "Payments", + column: "UserId", + unique: true); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.Designer.cs new file mode 100644 index 0000000..a573aae --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.Designer.cs @@ -0,0 +1,758 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241211115321_UpdateRatingColumn")] + partial class UpdateRatingColumn + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("LineItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.cs b/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.cs new file mode 100644 index 0000000..6f31cda --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241211115321_UpdateRatingColumn.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdateRatingColumn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "AverageRating_Value", + table: "Products", + type: "decimal(18,2)", + precision: 18, + scale: 2, + nullable: false, + oldClrType: typeof(double), + oldType: "float(18)", + oldPrecision: 18, + oldScale: 2 + ); + + migrationBuilder.AlterColumn( + name: "Rating_Value", + table: "ProductReviews", + type: "decimal(18,2)", + precision: 18, + scale: 2, + nullable: false, + oldClrType: typeof(double), + oldType: "float(18)", + oldPrecision: 18, + oldScale: 2 + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "AverageRating_Value", + table: "Products", + type: "float(18)", + precision: 18, + scale: 2, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)", + oldPrecision: 18, + oldScale: 2 + ); + + migrationBuilder.AlterColumn( + name: "Rating_Value", + table: "ProductReviews", + type: "float(18)", + precision: 18, + scale: 2, + nullable: false, + oldClrType: typeof(decimal), + oldType: "decimal(18,2)", + oldPrecision: 18, + oldScale: 2 + ); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.Designer.cs new file mode 100644 index 0000000..ebe8da7 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.Designer.cs @@ -0,0 +1,776 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241211135310_UpdateProductTable")] + partial class UpdateProductTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Colors") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Favourites") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ShortDescription") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Sizes") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("LineItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.cs b/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.cs new file mode 100644 index 0000000..af70866 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241211135310_UpdateProductTable.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdateProductTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "Products", + type: "nvarchar(50)", + maxLength: 50, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100 + ); + + migrationBuilder.AddColumn( + name: "Colors", + table: "Products", + type: "nvarchar(50)", + maxLength: 50, + nullable: false, + defaultValue: "" + ); + + migrationBuilder.AddColumn( + name: "Favourites", + table: "Products", + type: "int", + nullable: false, + defaultValue: 0 + ); + + migrationBuilder.AddColumn( + name: "ShortDescription", + table: "Products", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + defaultValue: "" + ); + + migrationBuilder.AddColumn( + name: "Sizes", + table: "Products", + type: "nvarchar(20)", + maxLength: 20, + nullable: false, + defaultValue: "" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "Colors", table: "Products"); + + migrationBuilder.DropColumn(name: "Favourites", table: "Products"); + + migrationBuilder.DropColumn(name: "ShortDescription", table: "Products"); + + migrationBuilder.DropColumn(name: "Sizes", table: "Products"); + + migrationBuilder.AlterColumn( + name: "Name", + table: "Products", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(50)", + oldMaxLength: 50 + ); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.Designer.cs new file mode 100644 index 0000000..f078d26 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.Designer.cs @@ -0,0 +1,795 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241212165458_UpdateDatabase")] + partial class UpdateDatabase + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("DeliveryMethod") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Colors") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Favourites") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ShortDescription") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Sizes") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.CartItem", "CartItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("ProductId"); + + b1.HasIndex("CartId", "ProductId") + .IsUnique(); + + b1.ToTable("CartItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("CartItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs new file mode 100644 index 0000000..97c3512 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdateDatabase : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "LineItems"); + + migrationBuilder.AddColumn( + name: "DeliveryMethod", + table: "Orders", + type: "int", + nullable: false, + defaultValue: 0 + ); + + migrationBuilder.AddColumn( + name: "Color", + table: "OrderItems", + type: "nvarchar(max)", + nullable: false, + defaultValue: "" + ); + + migrationBuilder.AddColumn( + name: "Size", + table: "OrderItems", + type: "nvarchar(max)", + nullable: false, + defaultValue: "" + ); + + migrationBuilder.CreateTable( + name: "CartItems", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CartId = table.Column(type: "uniqueidentifier", nullable: false), + ProductId = table.Column(type: "uniqueidentifier", nullable: false), + Color = table.Column(type: "nvarchar(max)", nullable: false), + Size = table.Column(type: "nvarchar(max)", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_CartItems", x => new { x.Id, x.CartId }); + table.ForeignKey( + name: "FK_CartItems_Carts_CartId", + column: x => x.CartId, + principalTable: "Carts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + table.ForeignKey( + name: "FK_CartItems_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateIndex( + name: "IX_CartItems_CartId_ProductId", + table: "CartItems", + columns: new[] { "CartId", "ProductId" }, + unique: true + ); + + migrationBuilder.CreateIndex( + name: "IX_CartItems_ProductId", + table: "CartItems", + column: "ProductId" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "CartItems"); + + migrationBuilder.DropColumn(name: "DeliveryMethod", table: "Orders"); + + migrationBuilder.DropColumn(name: "Color", table: "OrderItems"); + + migrationBuilder.DropColumn(name: "Size", table: "OrderItems"); + + migrationBuilder.CreateTable( + name: "LineItems", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CartId = table.Column(type: "uniqueidentifier", nullable: false), + ProductId = table.Column(type: "uniqueidentifier", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_LineItems", x => new { x.Id, x.CartId }); + table.ForeignKey( + name: "FK_LineItems_Carts_CartId", + column: x => x.CartId, + principalTable: "Carts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + table.ForeignKey( + name: "FK_LineItems_Products_ProductId", + column: x => x.ProductId, + principalTable: "Products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateIndex( + name: "IX_LineItems_CartId_ProductId", + table: "LineItems", + columns: new[] { "CartId", "ProductId" }, + unique: true + ); + + migrationBuilder.CreateIndex( + name: "IX_LineItems_ProductId", + table: "LineItems", + column: "ProductId" + ); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 5b4f4d9..be46d20 100644 --- a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -73,6 +73,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedOn") .HasColumnType("smalldatetime"); + b.Property("DeliveryMethod") + .HasColumnType("int"); + b.Property("ModifiedOn") .HasColumnType("smalldatetime"); @@ -115,23 +118,26 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(20) .HasColumnType("nvarchar(20)"); + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + b.Property("PaymentStatus") .HasColumnType("int"); b.Property("TransactionId") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); b.Property("UserId") .HasColumnType("uniqueidentifier"); b.HasKey("Id"); - b.HasIndex("OrderId") - .IsUnique(); + b.HasIndex("OrderId"); - b.HasIndex("UserId") - .IsUnique(); + b.HasIndex("UserId"); b.ToTable("Payments", (string)null); }); @@ -182,6 +188,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CategoryId") .HasColumnType("uniqueidentifier"); + b.Property("Colors") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + b.Property("CreatedOn") .HasColumnType("smalldatetime"); @@ -194,19 +205,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); + b.Property("Favourites") + .HasColumnType("int"); + b.Property("ModifiedOn") .HasColumnType("smalldatetime"); b.Property("Name") .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); b.Property("SKU") .IsRequired() .HasMaxLength(50) .HasColumnType("nvarchar(50)"); + b.Property("ShortDescription") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Sizes") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + b.Property("StockQuantity") .HasColumnType("int"); @@ -315,7 +339,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => { - b.OwnsMany("Shopizy.Domain.Carts.Entities.LineItem", "LineItems", b1 => + b.OwnsMany("Shopizy.Domain.Carts.Entities.CartItem", "CartItems", b1 => { b1.Property("Id") .HasColumnType("uniqueidentifier"); @@ -323,12 +347,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("CartId") .HasColumnType("uniqueidentifier"); + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b1.Property("ProductId") .HasColumnType("uniqueidentifier"); b1.Property("Quantity") .HasColumnType("int"); + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b1.HasKey("Id", "CartId"); b1.HasIndex("ProductId"); @@ -336,7 +368,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasIndex("CartId", "ProductId") .IsUnique(); - b1.ToTable("LineItems", (string)null); + b1.ToTable("CartItems", (string)null); b1.WithOwner() .HasForeignKey("CartId"); @@ -350,7 +382,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Navigation("Product"); }); - b.Navigation("LineItems"); + b.Navigation("CartItems"); }); modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => @@ -369,6 +401,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("OrderId") .HasColumnType("uniqueidentifier"); + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b1.Property("Discount") .HasPrecision(18, 2) .HasColumnType("decimal(18,2)"); @@ -384,6 +420,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("Quantity") .HasColumnType("int"); + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + b1.HasKey("Id", "OrderId"); b1.HasIndex("OrderId"); @@ -490,14 +530,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => { b.HasOne("Shopizy.Domain.Orders.Order", "Order") - .WithOne() - .HasForeignKey("Shopizy.Domain.Payments.Payment", "OrderId") + .WithMany() + .HasForeignKey("OrderId") .OnDelete(DeleteBehavior.NoAction) .IsRequired(); b.HasOne("Shopizy.Domain.Users.User", "User") - .WithOne() - .HasForeignKey("Shopizy.Domain.Payments.Payment", "UserId") + .WithMany() + .HasForeignKey("UserId") .OnDelete(DeleteBehavior.NoAction) .IsRequired(); @@ -578,7 +618,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Shopizy.Domain.Users.User", null) + b.HasOne("Shopizy.Domain.Users.User", "User") .WithMany("ProductReviews") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -589,9 +629,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("ProductReviewId") .HasColumnType("uniqueidentifier"); - b1.Property("Value") + b1.Property("Value") .HasPrecision(18, 2) - .HasColumnType("float(18)"); + .HasColumnType("decimal(18,2)"); b1.HasKey("ProductReviewId"); @@ -603,6 +643,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Rating") .IsRequired(); + + b.Navigation("User"); }); modelBuilder.Entity("Shopizy.Domain.Products.Product", b => @@ -621,9 +663,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property("NumRatings") .HasColumnType("int"); - b1.Property("Value") + b1.Property("Value") .HasPrecision(18, 2) - .HasColumnType("float(18)"); + .HasColumnType("decimal(18,2)"); b1.HasKey("ProductId"); diff --git a/src/Shopizy.Infrastructure/Payments/Persistence/PaymentConfigurations.cs b/src/Shopizy.Infrastructure/Payments/Persistence/PaymentConfigurations.cs index 7ef4d0e..3f7a6b9 100644 --- a/src/Shopizy.Infrastructure/Payments/Persistence/PaymentConfigurations.cs +++ b/src/Shopizy.Infrastructure/Payments/Persistence/PaymentConfigurations.cs @@ -25,7 +25,8 @@ private static void ConfigurePaymentsTable(EntityTypeBuilder builder) .HasConversion(id => id.Value, value => PaymentId.Create(value)); builder.Property(p => p.PaymentMethod).HasMaxLength(20); - builder.Property(p => p.TransactionId).HasMaxLength(50).IsRequired(false); + builder.Property(p => p.PaymentMethodId).HasMaxLength(260); + builder.Property(p => p.TransactionId).HasMaxLength(260).IsRequired(false); builder.Property(p => p.PaymentStatus); builder.Property(p => p.CreatedOn).HasColumnType("smalldatetime"); builder.Property(p => p.ModifiedOn).HasColumnType("smalldatetime").IsRequired(false); @@ -55,7 +56,7 @@ private static void ConfigurePaymentsTable(EntityTypeBuilder builder) .Property(p => p.OrderId) .HasConversion(id => id.Value, value => OrderId.Create(value)); - builder.HasOne(c => c.User).WithOne().OnDelete(DeleteBehavior.NoAction); - builder.HasOne(c => c.Order).WithOne().OnDelete(DeleteBehavior.NoAction); + builder.HasOne(c => c.User).WithMany().OnDelete(DeleteBehavior.NoAction); + builder.HasOne(c => c.Order).WithMany().OnDelete(DeleteBehavior.NoAction); } } diff --git a/src/Shopizy.Infrastructure/Products/Persistence/ProductConfigurations.cs b/src/Shopizy.Infrastructure/Products/Persistence/ProductConfigurations.cs index c1b23e1..8e67619 100644 --- a/src/Shopizy.Infrastructure/Products/Persistence/ProductConfigurations.cs +++ b/src/Shopizy.Infrastructure/Products/Persistence/ProductConfigurations.cs @@ -24,13 +24,16 @@ private static void ConfigureProductsTable(EntityTypeBuilder builder) .ValueGeneratedNever() .HasConversion(id => id.Value, value => ProductId.Create(value)); - builder.Property(p => p.Name).HasMaxLength(100).IsRequired(); + builder.Property(p => p.Name).HasMaxLength(50).IsRequired(); + builder.Property(p => p.ShortDescription).HasMaxLength(100); builder.Property(p => p.Description).HasMaxLength(200); builder.Property(p => p.SKU).HasMaxLength(50); builder.Property(p => p.StockQuantity); builder.Property(p => p.Discount).HasPrecision(18, 2).IsRequired(false); builder.Property(p => p.Brand).HasMaxLength(50).IsRequired(false); builder.Property(p => p.Barcode).HasMaxLength(50).IsRequired(false); + builder.Property(p => p.Colors).HasMaxLength(50).IsRequired(); + builder.Property(p => p.Sizes).HasMaxLength(20).IsRequired(); builder.Property(p => p.Tags).HasMaxLength(200).IsRequired(false); builder.Property(p => p.CreatedOn).HasColumnType("smalldatetime"); builder.Property(p => p.ModifiedOn).HasColumnType("smalldatetime").IsRequired(false); diff --git a/src/Shopizy.Infrastructure/Products/Persistence/ProductRepository.cs b/src/Shopizy.Infrastructure/Products/Persistence/ProductRepository.cs index a826ce4..3a8a29d 100644 --- a/src/Shopizy.Infrastructure/Products/Persistence/ProductRepository.cs +++ b/src/Shopizy.Infrastructure/Products/Persistence/ProductRepository.cs @@ -16,7 +16,7 @@ public class ProductRepository(AppDbContext dbContext) : IProductRepository public Task> GetProductsAsync( string? name, IList? categoryIds, - double? averageRating, + decimal? averageRating, int pageNumber, int pageSize ) @@ -29,7 +29,10 @@ int pageSize public Task GetProductByIdAsync(ProductId id) { - return _dbContext.Products.FirstOrDefaultAsync(p => p.Id == id); + return _dbContext + .Products.Include(p => p.ProductReviews) + .ThenInclude(p => p.User) + .FirstOrDefaultAsync(p => p.Id == id); } public Task> GetProductsByIdsAsync(IList ids) diff --git a/src/Shopizy.Infrastructure/Products/Specifications/ProductsByCriteriaSpec.cs b/src/Shopizy.Infrastructure/Products/Specifications/ProductsByCriteriaSpec.cs index 0caf30a..30950c9 100644 --- a/src/Shopizy.Infrastructure/Products/Specifications/ProductsByCriteriaSpec.cs +++ b/src/Shopizy.Infrastructure/Products/Specifications/ProductsByCriteriaSpec.cs @@ -9,7 +9,7 @@ internal class ProductsByCriteriaSpec : Specification public ProductsByCriteriaSpec( string? name, IList? categoryIds, - double? averageRating + decimal? averageRating ) : base(product => (name == null || product.Name.Contains(name)) diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.test.cs index 33edf55..fd7b964 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.test.cs @@ -3,6 +3,7 @@ using Shopizy.Application.Carts.Commands.AddProductToCart; using Shopizy.Application.Common.Interfaces.Persistence; using Shopizy.Application.UnitTests.Carts.TestUtils; +using Shopizy.Application.UnitTests.TestUtils.Constants; using Shopizy.Domain.Carts; using Shopizy.Domain.Carts.Entities; using Shopizy.Domain.Carts.ValueObjects; @@ -79,13 +80,13 @@ public async Task ShouldAddNewLineItemToCartWhenProductIsNotAlreadyPresentAsync( cart.Value.Should().NotBeNull(); cart.Value.Should().BeOfType(); cart.Value.Should().Be(existingCart); - cart.Value.LineItems.Should().HaveCount(1); - cart.Value.LineItems[0].ProductId.Should().BeOfType(typeof(ProductId)); - cart.Value.LineItems.Should() + cart.Value.CartItems.Should().HaveCount(1); + cart.Value.CartItems[0].ProductId.Should().BeOfType(typeof(ProductId)); + cart.Value.CartItems.Should() .Contain(li => li.ProductId == ProductId.Create(command.ProductId)); - cart.Value.LineItems[0].Quantity.Should().Be(1); + cart.Value.CartItems[0].Quantity.Should().Be(1); _mockCartRepository.Verify( - x => x.Update(It.Is(c => c.LineItems.Count == 1)), + x => x.Update(It.Is(c => c.CartItems.Count == 1)), Times.Once ); _mockCartRepository.Verify(x => x.Commit(CancellationToken.None), Times.Once); @@ -97,11 +98,23 @@ public async Task ShouldAddNewLineItemForAnotherProductAsync() { // Arrange var existingCart = CartFactory.Create(); - existingCart.AddLineItem(LineItem.Create(ProductId.CreateUnique())); + existingCart.AddLineItem( + CartItem.Create( + ProductId.CreateUnique(), + Constants.CartItem.Color, + Constants.CartItem.Size + ) + ); var updatedCart = CartFactory.Create(); - updatedCart.AddLineItem(LineItem.Create(ProductId.CreateUnique())); - updatedCart.AddLineItem(CartFactory.CreateLineItem()); + updatedCart.AddLineItem( + CartItem.Create( + ProductId.CreateUnique(), + Constants.CartItem.Color, + Constants.CartItem.Size + ) + ); + updatedCart.AddLineItem(CartFactory.CreateCartItem()); var command = AddProductToCartCommandUtils.CreateCommand(); @@ -127,17 +140,17 @@ public async Task ShouldAddNewLineItemForAnotherProductAsync() cart.Value.Should().NotBeNull(); cart.Value.Should().BeOfType(); cart.Value.Should().Be(updatedCart); - cart.Value.LineItems.Should().HaveCount(2); - cart.Value.LineItems.Should() + cart.Value.CartItems.Should().HaveCount(2); + cart.Value.CartItems.Should() .Contain(li => li.ProductId == ProductId.Create(command.ProductId)); - cart.Value.LineItems[0].ProductId.Should().BeOfType(typeof(ProductId)); - cart.Value.LineItems[0].Quantity.Should().Be(1); + cart.Value.CartItems[0].ProductId.Should().BeOfType(typeof(ProductId)); + cart.Value.CartItems[0].Quantity.Should().Be(1); _mockCartRepository.Verify( - x => x.Update(It.Is(c => c.LineItems.Count == 2)), + x => x.Update(It.Is(c => c.CartItems.Count == 2)), Times.Once ); _mockCartRepository.Verify(x => x.Commit(CancellationToken.None), Times.Once); - cart.Value.LineItems.Should() + cart.Value.CartItems.Should() .Contain(li => li.ProductId == ProductId.Create(command.ProductId) && li.Quantity == 1); } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.test.cs index f3f03e9..643d5b6 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/CreateCartWithFirstProduct/CreateCartWithFirstProductCommandHandler.test.cs @@ -58,7 +58,7 @@ public async Task ShouldCreatesCartWithLineItemAndReturnsWhenProductExistsAsync( { // Arrange var cart = CartFactory.Create(); - cart.AddLineItem(CartFactory.CreateLineItem()); + cart.AddLineItem(CartFactory.CreateCartItem()); var command = CreateCartWithFirstProductCommandUtils.CreateCommand(); _mockProductRepository .Setup(x => x.IsProductExistAsync(ProductId.Create(command.ProductId))) @@ -79,7 +79,7 @@ public async Task ShouldCreatesCartWithLineItemAndReturnsWhenProductExistsAsync( _mockCartRepository.Verify( x => x.AddAsync( - It.Is(c => c.UserId.Value == command.UserId && c.LineItems.Count == 1) + It.Is(c => c.UserId.Value == command.UserId && c.CartItems.Count == 1) ), Times.Once ); @@ -90,7 +90,7 @@ public async Task ShouldCreatesCartWithLineItemAndReturnsWhenProductExistsAsync( result.Value.Should().NotBeNull(); result.Value.Should().BeOfType(typeof(Cart)); result.Value.UserId.Should().Be(UserId.Create(command.UserId)); - result.Value.LineItems.Should().HaveCount(1); - result.Value.LineItems[0].ProductId.Should().Be(ProductId.Create(command.ProductId)); + result.Value.CartItems.Should().HaveCount(1); + result.Value.CartItems[0].ProductId.Should().Be(ProductId.Create(command.ProductId)); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.test.cs index 1ebe155..3d76be2 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.test.cs @@ -74,7 +74,7 @@ public async Task ShouldRemoveLastLineItemFromCartWhenCartIsFoundAsync() Times.Once ); _mockCartRepository.Verify( - x => x.Update(It.Is(c => c.LineItems.Count == 0)), + x => x.Update(It.Is(c => c.CartItems.Count == 0)), Times.Once ); _mockCartRepository.Verify(x => x.Commit(It.IsAny()), Times.Once); diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Queries/GetCart/GetCartQueryHandler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Queries/GetCart/GetCartQueryHandler.test.cs index b352f40..4916121 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Queries/GetCart/GetCartQueryHandler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/Queries/GetCart/GetCartQueryHandler.test.cs @@ -25,7 +25,7 @@ public async Task ShouldReturnValidCartWhenUserHasItemsInCartAsync() { // Arrange var cart = CartFactory.Create(); - cart.AddLineItem(CartFactory.CreateLineItem()); + cart.AddLineItem(CartFactory.CreateCartItem()); var query = GetCartQueryUtils.CreateQuery(); _mockCartRepository diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/AddProductToCartCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/AddProductToCartCommandUtils.cs index 3cf1b16..19ab44e 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/AddProductToCartCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/AddProductToCartCommandUtils.cs @@ -10,7 +10,9 @@ public static AddProductToCartCommand CreateCommand() return new AddProductToCartCommand( Constants.User.Id.Value, Constants.Cart.Id.Value, - Constants.Product.Id.Value + Constants.Product.Id.Value, + Constants.CartItem.Color, + Constants.CartItem.Size ); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CartFactory.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CartFactory.cs index 8fd3d37..edbc206 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CartFactory.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CartFactory.cs @@ -11,8 +11,12 @@ public static Cart Create() return Cart.Create(Constants.User.Id); } - public static LineItem CreateLineItem() + public static CartItem CreateCartItem() { - return LineItem.Create(Constants.Product.Id); + return CartItem.Create( + Constants.CartItem.ProductId, + Constants.CartItem.Color, + Constants.CartItem.Size + ); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CreateCartWithFirstProductCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CreateCartWithFirstProductCommandUtils.cs index dc2931d..48ac152 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CreateCartWithFirstProductCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/CreateCartWithFirstProductCommandUtils.cs @@ -9,7 +9,9 @@ public static CreateCartWithFirstProductCommand CreateCommand() { return new CreateCartWithFirstProductCommand( Constants.User.Id.Value, - Constants.Product.Id.Value + Constants.Product.Id.Value, + Constants.CartItem.Color, + Constants.CartItem.Size ); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/CreateOrderCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/CreateOrderCommandUtils.cs index 8acb693..25801d8 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/CreateOrderCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/CreateOrderCommandUtils.cs @@ -18,6 +18,7 @@ public static CreateOrderCommand CreateCommand(IList productIds) return new CreateOrderCommand( Constants.User.Id.Value, Constants.Order.PromoCode, + Constants.Order.DeliveryMethod, Constants.Order.DeliveryCharge.Amount, Constants.Order.DeliveryCharge.Currency, orderItems, @@ -29,7 +30,12 @@ public static IEnumerable CreateOrderItemCommand(IList p { foreach (var productId in productIds) { - yield return new OrderItemCommand(productId, 1); + yield return new OrderItemCommand( + productId, + Constants.Order.Color, + Constants.Order.Size, + 1 + ); } } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/OrderFactory.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/OrderFactory.cs index 5cbfe02..0569a5c 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/OrderFactory.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Orders/TestUtils/OrderFactory.cs @@ -10,6 +10,7 @@ public static Order CreateOrder() return Order.Create( Constants.User.Id, Constants.Order.PromoCode, + Constants.Order.DeliveryMethod, Constants.Order.DeliveryCharge, Constants.User.Address, [] diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Products/Queries/GetProducts/GetProductsQueryHandler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Products/Queries/GetProducts/GetProductsQueryHandler.test.cs index 0349c58..178b790 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Products/Queries/GetProducts/GetProductsQueryHandler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Products/Queries/GetProducts/GetProductsQueryHandler.test.cs @@ -28,7 +28,7 @@ public async Task ShouldReturnErrorWhenNoProductsAreAvailableAsync() x.GetProductsAsync( It.IsAny(), It.IsAny>(), - It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny() ) diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/CreateProductCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/CreateProductCommandUtils.cs index d9a6f7f..3f6b0d1 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/CreateProductCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/CreateProductCommandUtils.cs @@ -10,6 +10,7 @@ public static CreateProductCommand CreateCommand() return new CreateProductCommand( Constants.User.Id.Value, Constants.Product.Name, + Constants.Product.ShortDescription, Constants.Product.Description, Constants.Category.Id.Value, Constants.Product.UnitPrice, @@ -17,6 +18,8 @@ public static CreateProductCommand CreateCommand() Constants.Product.Discount, Constants.Product.Sku, Constants.Product.Brand, + Constants.Product.Colors, + Constants.Product.Sizes, Constants.Product.Tags, Constants.Product.Barcode, [] @@ -28,6 +31,7 @@ public static CreateProductCommand CreateCommandWithEmptyProductName() return new CreateProductCommand( Constants.User.Id.Value, "", + Constants.Product.ShortDescription, Constants.Product.Description, Constants.Category.Id.Value, Constants.Product.UnitPrice, @@ -36,6 +40,8 @@ public static CreateProductCommand CreateCommandWithEmptyProductName() Constants.Product.Sku, Constants.Product.Brand, Constants.Product.Tags, + Constants.Product.Colors, + Constants.Product.Sizes, Constants.Product.Barcode, [] ); diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/ProductFactory.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/ProductFactory.cs index b05b100..0e9f5e7 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/ProductFactory.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/ProductFactory.cs @@ -10,6 +10,7 @@ public static Product CreateProduct() { return Product.Create( Constants.Product.Name, + Constants.Product.ShortDescription, Constants.Product.Description, Constants.Category.Id, Constants.Product.Sku, @@ -17,6 +18,8 @@ public static Product CreateProduct() Constants.Product.Discount, Constants.Product.Brand, Constants.Product.Tags, + Constants.Product.Colors, + Constants.Product.Sizes, Constants.Product.Barcode ); } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/UpdateProductCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/UpdateProductCommandUtils.cs index f811508..416a1e4 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/UpdateProductCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Products/TestUtils/UpdateProductCommandUtils.cs @@ -11,6 +11,7 @@ public static UpdateProductCommand CreateCommand() Constants.User.Id.Value, Constants.Product.Id.Value, Constants.Product.Name, + Constants.Product.ShortDescription, Constants.Product.Description, Constants.Category.Id.Value, Constants.Product.UnitPrice, @@ -18,6 +19,8 @@ public static UpdateProductCommand CreateCommand() Constants.Product.Discount, Constants.Product.Sku, Constants.Product.Brand, + Constants.Product.Colors, + Constants.Product.Sizes, Constants.Product.Tags, Constants.Product.Barcode, [] diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Cart.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Cart.cs index d30b3f5..52ad50d 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Cart.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Cart.cs @@ -12,12 +12,14 @@ public static class Cart ); } - public static class LineItem + public static class CartItem { - public static readonly LineItemId Id = LineItemId.Create( + public static readonly CartItemId Id = CartItemId.Create( new Guid("dd0aa32a-f7ab-4d48-b33e-1a3c1092f1e2") ); public static readonly ProductId ProductId = Product.Id; + public const string Color = "White"; + public const string Size = "XL"; public const int Quantity = 5; } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Order.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Order.cs index eddb4bf..0fff393 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Order.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Order.cs @@ -11,7 +11,10 @@ public static class Order new Guid("dd0aa32a-f7ab-4d48-b33e-1a3c1092f1e2") ); public const string PromoCode = "WELCOME"; + public const int DeliveryMethod = 1; public static readonly Price DeliveryCharge = Price.CreateNew(100, 0); public const string CancellationReason = "Cancel Reason"; + public const string Color = "White"; + public const string Size = "XL"; } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Product.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Product.cs index b07343c..de28d6a 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Product.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.Product.cs @@ -11,12 +11,15 @@ public static class Product new Guid("dd0aa32a-f7ab-4d48-b33e-1a3c1092f1e2") ); public const string Name = "Product Name"; + public const string ShortDescription = "Product Short Description"; public const string Description = "Product Description"; public const decimal UnitPrice = 100; public const int Currency = 0; public const int Discount = 30; public const string Sku = "Product SKU"; public const string Brand = "Product Brand Name"; + public const string Colors = "White,Red,Green,Blue"; + public const string Sizes = "S,M,L,XL"; public const string Tags = "Product Tag"; public const string Barcode = "Product Barcode"; public const int StockQuantity = 50; diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs index 0947bab..a50316b 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs @@ -15,8 +15,8 @@ public static class User public const string LastName = "Doe"; public const string ProfileImageUrl = ""; public const string Phone = "1234567890"; - public const string Password = "password"; - public const string NewPassword = "password"; + public const string Password = "oldPassword"; + public const string NewPassword = "newPassword"; public static readonly Address Address = Address.CreateNew( "Street", "City", diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Extensions/CartExtensions.Validations.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Extensions/CartExtensions.Validations.cs index 4bb311a..c198ce8 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Extensions/CartExtensions.Validations.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Extensions/CartExtensions.Validations.cs @@ -9,6 +9,6 @@ public static partial class CartExtensions public static void ValidateResult(this Cart cart, GetCartQuery query) { cart.UserId.Value.Should().Be(query.UserId); - cart.LineItems.Should().HaveCount(1); + cart.CartItems.Should().HaveCount(1); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs index 4874804..e8b6075 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/Commands/UpdatePassword/UpdatePasswordCommandHadler.test.cs @@ -27,38 +27,37 @@ public UpdatePasswordCommandHandlerTests() ); } - [Fact] - public async Task ShouldThrowExceptionWhenUserIdIsNotProvidedAsync() - { - // Arrange - var command = UpdatePasswordCommandUtils.CreateCommandWithEmptyUserId(); + // [Fact] + // public async Task ShouldThrowExceptionWhenUserIdIsNotProvidedAsync() + // { + // // Arrange + // var command = UpdatePasswordCommandUtils.CreateCommandWithEmptyUserId(); - _mockUserRepository - .Setup(u => u.GetUserById(UserId.Create(command.UserId))) - .ReturnsAsync(() => null); + // _mockUserRepository + // .Setup(u => u.GetUserById(UserId.Create(command.UserId))) + // .ReturnsAsync(() => null); - // Act - var result = await _sut.Handle(command, CancellationToken.None); + // // Act + // var result = await _sut.Handle(command, CancellationToken.None); - // Assert - result.IsError.Should().BeTrue(); - result.Errors.Should().NotBeNullOrEmpty(); - result.Errors[0].Should().Be(CustomErrors.User.UserNotFound); - } + // // Assert + // result.IsError.Should().BeTrue(); + // result.Errors.Should().NotBeNullOrEmpty(); + // result.Errors[0].Should().Be(CustomErrors.User.UserNotFound); + // } [Fact] public async Task ShouldReturnErrorWhenUserIsNotFoundAsync() { // Arrange var user = UserFactory.CreateUser(); - var updatedUser = UserFactory.UpdatePassword(user); var command = UpdatePasswordCommandUtils.CreateCommand(); _mockUserRepository .Setup(u => u.GetUserById(UserId.Create(command.UserId))) .ReturnsAsync(() => null); - _mockUserRepository.Setup(u => u.Update(updatedUser)); + _mockUserRepository.Setup(u => u.Update(user)); _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); @@ -107,7 +106,6 @@ public async Task ShouldUpdatePasswordWhenOldPasswordIsCorrectAndNewPasswordIsPr { // Arrange var user = UserFactory.CreateUser(); - var updatedUser = UserFactory.UpdatePassword(user); var command = UpdatePasswordCommandUtils.CreateCommand(); _mockUserRepository @@ -115,14 +113,14 @@ public async Task ShouldUpdatePasswordWhenOldPasswordIsCorrectAndNewPasswordIsPr .ReturnsAsync(user); _mockPasswordManager - .Setup(u => u.Verify(user.Password ?? "", Constants.User.NewPassword)) + .Setup(u => u.Verify(user.Password ?? "", Constants.User.Password)) .Returns(true); _mockPasswordManager .Setup(u => u.CreateHashString(Constants.User.NewPassword, 10000)) .Returns("hashstring"); - _mockUserRepository.Setup(u => u.Update(updatedUser)); + _mockUserRepository.Setup(u => u.Update(user)); _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); @@ -145,7 +143,6 @@ public async Task ShouldReturnErrorWhenNewPasswordIsSameAsOldPasswordAsync() // Arrange var command = UpdatePasswordCommandUtils.CreateCommandWithSameOldAndNewPassword(); var user = UserFactory.CreateUser(); - var updatedUser = UserFactory.UpdatePassword(user); _mockUserRepository.Setup(x => x.GetUserById(It.IsAny())).ReturnsAsync(user); @@ -157,7 +154,7 @@ public async Task ShouldReturnErrorWhenNewPasswordIsSameAsOldPasswordAsync() .Setup(u => u.CreateHashString(It.IsAny(), 10000)) .Returns("hashstring"); - _mockUserRepository.Setup(u => u.Update(updatedUser)); + _mockUserRepository.Setup(u => u.Update(user)); _mockUserRepository.Setup(x => x.Commit(It.IsAny())).ReturnsAsync(1); diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs index d9708ce..956e925 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UpdatePasswordCommandUtils.cs @@ -22,13 +22,4 @@ public static UpdatePasswordCommand CreateCommandWithSameOldAndNewPassword() Constants.User.Password ); } - - public static UpdatePasswordCommand CreateCommandWithEmptyUserId() - { - return new UpdatePasswordCommand( - Constants.User.Id.Value, - Constants.User.Password, - Constants.User.Password - ); - } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs index b53c25a..b1faff8 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs @@ -20,10 +20,4 @@ public static User UpdateAddress(User user) user.UpdateAddress(Constants.User.Address); return user; } - - public static User UpdatePassword(User user) - { - user.UpdatePassword(Constants.User.NewPassword); - return user; - } } From 62c8c9b4f3103f3c6633f4091ab6132e17aebaf5 Mon Sep 17 00:00:00 2001 From: "Md. Abul Kalam" Date: Tue, 17 Dec 2024 23:30:51 +0600 Subject: [PATCH 3/4] Issue #56: Update payment endpoint --- .../Common/Mapping/CartMappingConfig.cs | 7 +- .../Common/Mapping/paymentMappingConfig.cs | 9 + src/Shopizy.Api/Controllers/CartController.cs | 15 +- .../Controllers/PaymentController.cs | 26 +- .../AddProductToCartCommandHandler.cs | 8 +- .../RemoveProductFromCartCommand.cs | 2 +- .../RemoveProductFromCartCommandHandler.cs | 2 +- .../UpdateProductQuantityCommand.cs | 9 +- .../UpdateProductQuantityCommandHandler.cs | 3 +- .../CardNotPresentSaleCommand.cs | 2 +- .../CardNotPresentSaleCommandHandler.cs | 2 + .../CashOnDeliverySaleCommand.cs | 15 + .../CashOnDeliverySaleCommandHandler.cs | 69 ++ src/Shopizy.Contracts/Order/OrderResponse.cs | 3 + .../Payment/CardNotPresentSaleRequest.cs | 8 +- src/Shopizy.Domain/Carts/Cart.cs | 4 +- src/Shopizy.Domain/Common/Enums/Currency.cs | 2 +- src/Shopizy.Domain/Orders/Order.cs | 6 + .../Payments/Enums/PaymentStatus.cs | 2 +- .../Carts/Persistence/CartConfigurations.cs | 1 - ...213084430_UpdateCartItemsTable.Designer.cs | 794 ++++++++++++++++++ .../20241213084430_UpdateCartItemsTable.cs | 35 + .../Migrations/AppDbContextModelSnapshot.cs | 5 +- .../RemoveProductFromCartCommandUtils.cs | 2 +- 24 files changed, 994 insertions(+), 37 deletions(-) create mode 100644 src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommand.cs create mode 100644 src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommandHandler.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.cs diff --git a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs index 5050420..b46720b 100644 --- a/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/CartMappingConfig.cs @@ -36,18 +36,19 @@ public void Register(TypeAdapterConfig config) config .NewConfig< - (Guid UserId, Guid CartId, UpdateProductQuantityRequest request), + (Guid UserId, Guid CartId, Guid ItemId, UpdateProductQuantityRequest request), UpdateProductQuantityCommand >() .Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest.CartId, src => src.CartId) + .Map(dest => dest.ItemId, src => src.ItemId) .Map(dest => dest, src => src.request); config - .NewConfig<(Guid UserId, Guid CartId, Guid ProductId), RemoveProductFromCartCommand>() + .NewConfig<(Guid UserId, Guid CartId, Guid ItemId), RemoveProductFromCartCommand>() .Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest.CartId, src => src.CartId) - .Map(dest => dest.ProductId, src => src.ProductId); + .Map(dest => dest.ItemId, src => src.ItemId); config.NewConfig().MapWith(userId => new GetCartQuery(userId)); diff --git a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs index 5348911..f17b5f8 100644 --- a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs @@ -2,6 +2,7 @@ using Mapster; using Shopizy.Application.Common.models; using Shopizy.Application.Payments.Commands.CardNotPresentSale; +using Shopizy.Application.Payments.Commands.CashOnDeliverySale; using Shopizy.Contracts.Payment; namespace Shopizy.Api.Common.Mapping; @@ -19,5 +20,13 @@ public void Register(TypeAdapterConfig config) >() .Map(dest => dest.UserId, src => src.UserId) .Map(dest => dest, src => src.request); + + config + .NewConfig< + (Guid UserId, CardNotPresentSaleRequest request), + CashOnDeliverySaleCommand + >() + .Map(dest => dest.UserId, src => src.UserId) + .Map(dest => dest, src => src.request); } } diff --git a/src/Shopizy.Api/Controllers/CartController.cs b/src/Shopizy.Api/Controllers/CartController.cs index 14dc1ab..4c796a8 100644 --- a/src/Shopizy.Api/Controllers/CartController.cs +++ b/src/Shopizy.Api/Controllers/CartController.cs @@ -66,7 +66,7 @@ AddProductToCartRequest request return result.Match(product => Ok(_mapper.Map(product)), Problem); } - [HttpPatch("{cartId:guid}/update-quantity")] + [HttpPatch("{cartId:guid}/items/{itemId:guid}")] [SwaggerResponse(StatusCodes.Status200OK, null, typeof(SuccessResult))] [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] @@ -75,10 +75,11 @@ AddProductToCartRequest request public async Task UpdateProductQuantityAsync( Guid userId, Guid cartId, + Guid itemId, UpdateProductQuantityRequest request ) { - var command = _mapper.Map((userId, cartId, request)); + var command = _mapper.Map((userId, cartId, itemId, request)); var result = await _mediator.Send(command); return result.Match( @@ -87,19 +88,15 @@ UpdateProductQuantityRequest request ); } - [HttpDelete("{cartId:guid}/product/{productId:guid}")] + [HttpDelete("{cartId:guid}/items/{itemId:guid}")] [SwaggerResponse(StatusCodes.Status200OK, null, typeof(SuccessResult))] [SwaggerResponse(StatusCodes.Status400BadRequest, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status401Unauthorized, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status409Conflict, null, typeof(ErrorResult))] [SwaggerResponse(StatusCodes.Status500InternalServerError, null, typeof(ErrorResult))] - public async Task RemoveProductFromCartAsync( - Guid userId, - Guid cartId, - Guid productId - ) + public async Task RemoveItemFromCartAsync(Guid userId, Guid cartId, Guid itemId) { - var command = _mapper.Map((userId, cartId, productId)); + var command = _mapper.Map((userId, cartId, itemId)); var result = await _mediator.Send(command); return result.Match( diff --git a/src/Shopizy.Api/Controllers/PaymentController.cs b/src/Shopizy.Api/Controllers/PaymentController.cs index 29d68e4..b4ff1f3 100644 --- a/src/Shopizy.Api/Controllers/PaymentController.cs +++ b/src/Shopizy.Api/Controllers/PaymentController.cs @@ -2,6 +2,7 @@ using MediatR; using Microsoft.AspNetCore.Mvc; using Shopizy.Application.Payments.Commands.CardNotPresentSale; +using Shopizy.Application.Payments.Commands.CashOnDeliverySale; using Shopizy.Contracts.Common; using Shopizy.Contracts.Payment; using Swashbuckle.AspNetCore.Annotations; @@ -22,12 +23,25 @@ public class PaymentController(ISender mediator, IMapper mapper) : ApiController [SwaggerResponse(StatusCodes.Status500InternalServerError, null, typeof(ErrorResult))] public async Task CreateSaleAsync(Guid userId, CardNotPresentSaleRequest request) { - var command = _mapper.Map((userId, request)); - var result = await _mediator.Send(command); + if (request.PaymentMethod.ToLower() == "Card") + { + var command = _mapper.Map((userId, request)); + var result = await _mediator.Send(command); - return result.Match( - success => Ok(SuccessResult.Success("Payment successfull collected.")), - Problem - ); + return result.Match( + success => Ok(SuccessResult.Success("Payment successfull collected.")), + Problem + ); + } + else + { + var command = _mapper.Map((userId, request)); + var result = await _mediator.Send(command); + + return result.Match( + success => Ok(SuccessResult.Success("Payment successfull collected.")), + Problem + ); + } } } diff --git a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs index 3c10af4..50e8a97 100644 --- a/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/AddProductToCart/AddProductToCartCommandHandler.cs @@ -33,7 +33,13 @@ CancellationToken cancellationToken return CustomErrors.Cart.CartNotFound; } - if (cart.CartItems.Any(li => li.ProductId == ProductId.Create(cmd.ProductId))) + if ( + cart.CartItems.Any(li => + li.ProductId == ProductId.Create(cmd.ProductId) + && li.Color == cmd.Color + && li.Size == cmd.Size + ) + ) { return CustomErrors.Cart.ProductAlreadyExistInCart; } diff --git a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommand.cs b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommand.cs index 6452021..2528697 100644 --- a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommand.cs +++ b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommand.cs @@ -6,5 +6,5 @@ namespace Shopizy.Application.Carts.Commands.RemoveProductFromCart; [Authorize(Permissions = Permissions.Cart.Delete, Policies = Policy.SelfOrAdmin)] -public record RemoveProductFromCartCommand(Guid UserId, Guid CartId, Guid ProductId) +public record RemoveProductFromCartCommand(Guid UserId, Guid CartId, Guid ItemId) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs index 00018e3..6dc9aab 100644 --- a/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/RemoveProductFromCart/RemoveProductFromCartCommandHandler.cs @@ -26,7 +26,7 @@ CancellationToken cancellationToken return CustomErrors.Cart.CartNotFound; } - var lineItem = cart.CartItems.FirstOrDefault(li => li.ProductId.Value == cmd.ProductId); + var lineItem = cart.CartItems.FirstOrDefault(li => li.Id.Value == cmd.ItemId); if (lineItem is not null) { cart.RemoveLineItem(lineItem); diff --git a/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommand.cs b/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommand.cs index 85ed29f..3c7d9d0 100644 --- a/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommand.cs +++ b/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommand.cs @@ -6,5 +6,10 @@ namespace Shopizy.Application.Carts.Commands.UpdateProductQuantity; [Authorize(Permissions = Permissions.Cart.Modify, Policies = Policy.SelfOrAdmin)] -public record UpdateProductQuantityCommand(Guid UserId, Guid CartId, Guid ProductId, int Quantity) - : IAuthorizeableRequest>; +public record UpdateProductQuantityCommand( + Guid UserId, + Guid CartId, + Guid ItemId, + Guid ProductId, + int Quantity +) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommandHandler.cs b/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommandHandler.cs index 904ada3..3f37961 100644 --- a/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommandHandler.cs +++ b/src/Shopizy.Application/Carts/Commands/UpdateProductQuantity/UpdateProductQuantityCommandHandler.cs @@ -3,7 +3,6 @@ using Shopizy.Application.Common.Interfaces.Persistence; using Shopizy.Domain.Carts.ValueObjects; using Shopizy.Domain.Common.CustomErrors; -using Shopizy.Domain.Products.ValueObjects; namespace Shopizy.Application.Carts.Commands.UpdateProductQuantity; @@ -27,7 +26,7 @@ CancellationToken cancellationToken return CustomErrors.Cart.CartNotFound; } - cart.UpdateLineItem(ProductId.Create(cmd.ProductId), cmd.Quantity); + cart.UpdateLineItem(CartItemId.Create(cmd.ItemId), cmd.Quantity); _cartRepository.Update(cart); diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs index 7bd9213..fbb7aed 100644 --- a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs @@ -10,7 +10,7 @@ namespace Shopizy.Application.Payments.Commands.CardNotPresentSale; public record CardNotPresentSaleCommand( Guid UserId, Guid OrderId, - double Amount, + decimal Amount, string Currency, string PaymentMethod, string PaymentMethodId, diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs index 30abdcf..b634e8c 100644 --- a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommandHandler.cs @@ -4,6 +4,7 @@ using Shopizy.Application.Common.Interfaces.Services; using Shopizy.Application.Common.models; using Shopizy.Domain.Common.CustomErrors; +using Shopizy.Domain.Orders.Enums; using Shopizy.Domain.Orders.ValueObjects; using Shopizy.Domain.Payments; using Shopizy.Domain.Payments.Enums; @@ -101,6 +102,7 @@ CancellationToken cancellationToken } order.UpdatePaymentStatus(PaymentStatus.Payed); + order.UpdateOrderStatus(OrderStatus.Paid); payment.UpdatePaymentStatus(PaymentStatus.Payed); payment.UpdateTransactionId(response.Value.ChargeId); diff --git a/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommand.cs b/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommand.cs new file mode 100644 index 0000000..cd61c27 --- /dev/null +++ b/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommand.cs @@ -0,0 +1,15 @@ +using ErrorOr; +using Shopizy.Application.Common.Security.Permissions; +using Shopizy.Application.Common.Security.Policies; +using Shopizy.Application.Common.Security.Request; + +namespace Shopizy.Application.Payments.Commands.CashOnDeliverySale; + +[Authorize(Permissions = Permissions.Order.Get, Policies = Policy.SelfOrAdmin)] +public record CashOnDeliverySaleCommand( + Guid UserId, + Guid OrderId, + decimal Amount, + string Currency, + string PaymentMethod +) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommandHandler.cs b/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommandHandler.cs new file mode 100644 index 0000000..2512b66 --- /dev/null +++ b/src/Shopizy.Application/Payments/Commands/CashOnDeliverySale/CashOnDeliverySaleCommandHandler.cs @@ -0,0 +1,69 @@ +using ErrorOr; +using MediatR; +using Shopizy.Application.Common.Interfaces.Persistence; +using Shopizy.Domain.Common.CustomErrors; +using Shopizy.Domain.Common.Enums; +using Shopizy.Domain.Common.ValueObjects; +using Shopizy.Domain.Orders.Enums; +using Shopizy.Domain.Orders.ValueObjects; +using Shopizy.Domain.Payments; +using Shopizy.Domain.Payments.Enums; +using Shopizy.Domain.Users.ValueObjects; + +namespace Shopizy.Application.Payments.Commands.CashOnDeliverySale; + +public class CashOnDeliverySaleCommandHandler( + IPaymentRepository paymentRepository, + IOrderRepository orderRepository +) : IRequestHandler> +{ + private readonly IPaymentRepository _paymentRepository = paymentRepository; + private readonly IOrderRepository _orderRepository = orderRepository; + + public async Task> Handle( + CashOnDeliverySaleCommand request, + CancellationToken cancellationToken + ) + { + try + { + var order = await _orderRepository.GetOrderByIdAsync(OrderId.Create(request.OrderId)); + + if (order is null) + { + return CustomErrors.Order.OrderNotFound; + } + + var payment = Payment.Create( + UserId.Create(request.UserId), + OrderId.Create(request.OrderId), + request.PaymentMethod, + "", + "", + PaymentStatus.Pending, + Price.CreateNew(request.Amount, Currency.usd), + order.ShippingAddress + ); + + await _paymentRepository.AddAsync(payment); + + if (await _paymentRepository.Commit(cancellationToken) <= 0) + { + return CustomErrors.Payment.PaymentNotCreated; + } + order.UpdateOrderStatus(OrderStatus.AwaitingValidation); + + _orderRepository.Update(order); + await _orderRepository.Commit(cancellationToken); + + return Result.Success; + } + catch (Exception ex) + { + return Error.Failure( + code: "payment.failed", + description: "Failed to collect the payment." + ); + } + } +} diff --git a/src/Shopizy.Contracts/Order/OrderResponse.cs b/src/Shopizy.Contracts/Order/OrderResponse.cs index e0ea18a..750ff4c 100644 --- a/src/Shopizy.Contracts/Order/OrderResponse.cs +++ b/src/Shopizy.Contracts/Order/OrderResponse.cs @@ -16,7 +16,10 @@ DateTime ModifiedOn public record OrderItemResponse( Guid OrderItemId, string Name, + Price UnitPrice, string PictureUrl, + string Color, + string Size, int Quantity, decimal Discount ); diff --git a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs index 4ee3843..bf23934 100644 --- a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs +++ b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs @@ -2,10 +2,14 @@ namespace Shopizy.Contracts.Payment; public record CardNotPresentSaleRequest( Guid OrderId, - double Amount, + decimal Amount, string Currency, string PaymentMethod, - string PaymentMethodId, + string? PaymentMethodId, + CardInfo? CardInfo +); + +public record CardInfo( string CardName, string CardExpiryMonth, string CardExpiryYear, diff --git a/src/Shopizy.Domain/Carts/Cart.cs b/src/Shopizy.Domain/Carts/Cart.cs index cd63036..e856cac 100644 --- a/src/Shopizy.Domain/Carts/Cart.cs +++ b/src/Shopizy.Domain/Carts/Cart.cs @@ -30,9 +30,9 @@ public void RemoveLineItem(CartItem lineItem) ModifiedOn = DateTime.UtcNow; } - public void UpdateLineItem(ProductId productId, int quantity) + public void UpdateLineItem(CartItemId cartItemId, int quantity) { - _cartItems.Find(li => li.ProductId == productId)?.UpdateQuantity(quantity); + _cartItems.Find(li => li.Id == cartItemId)?.UpdateQuantity(quantity); ModifiedOn = DateTime.UtcNow; } diff --git a/src/Shopizy.Domain/Common/Enums/Currency.cs b/src/Shopizy.Domain/Common/Enums/Currency.cs index c11be8a..1d89c4a 100644 --- a/src/Shopizy.Domain/Common/Enums/Currency.cs +++ b/src/Shopizy.Domain/Common/Enums/Currency.cs @@ -4,5 +4,5 @@ public enum Currency { usd = 0, bdt = 1, - euro = 2 + euro = 2, } diff --git a/src/Shopizy.Domain/Orders/Order.cs b/src/Shopizy.Domain/Orders/Order.cs index 90270c1..d9108f0 100644 --- a/src/Shopizy.Domain/Orders/Order.cs +++ b/src/Shopizy.Domain/Orders/Order.cs @@ -89,4 +89,10 @@ public void UpdatePaymentStatus(PaymentStatus status) PaymentStatus = status; ModifiedOn = DateTime.UtcNow; } + + public void UpdateOrderStatus(OrderStatus status) + { + OrderStatus = status; + ModifiedOn = DateTime.UtcNow; + } } diff --git a/src/Shopizy.Domain/Payments/Enums/PaymentStatus.cs b/src/Shopizy.Domain/Payments/Enums/PaymentStatus.cs index 470758b..6139e88 100644 --- a/src/Shopizy.Domain/Payments/Enums/PaymentStatus.cs +++ b/src/Shopizy.Domain/Payments/Enums/PaymentStatus.cs @@ -4,5 +4,5 @@ public enum PaymentStatus { Pending = 1, Cancelled = 2, - Payed = 3 + Payed = 3, } diff --git a/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs b/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs index f98ce0b..e99ad9e 100644 --- a/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs +++ b/src/Shopizy.Infrastructure/Carts/Persistence/CartConfigurations.cs @@ -45,7 +45,6 @@ private static void ConfigureCartItemsTable(EntityTypeBuilder builder) ci.ToTable("CartItems"); ci.WithOwner().HasForeignKey("CartId"); ci.HasKey(nameof(CartItem.Id), "CartId"); - ci.HasIndex("CartId", "ProductId").IsUnique(); ci.Property(li => li.Id) .ValueGeneratedNever() diff --git a/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.Designer.cs new file mode 100644 index 0000000..02fea69 --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.Designer.cs @@ -0,0 +1,794 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241213084430_UpdateCartItemsTable")] + partial class UpdateCartItemsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("DeliveryMethod") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Colors") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Favourites") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ShortDescription") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Sizes") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.CartItem", "CartItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("CartId"); + + b1.HasIndex("ProductId"); + + b1.ToTable("CartItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("CartItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.cs b/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.cs new file mode 100644 index 0000000..e85f58b --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241213084430_UpdateCartItemsTable.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class UpdateCartItemsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex(name: "IX_CartItems_CartId_ProductId", table: "CartItems"); + + migrationBuilder.CreateIndex( + name: "IX_CartItems_CartId", + table: "CartItems", + column: "CartId" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex(name: "IX_CartItems_CartId", table: "CartItems"); + + migrationBuilder.CreateIndex( + name: "IX_CartItems_CartId_ProductId", + table: "CartItems", + columns: new[] { "CartId", "ProductId" }, + unique: true + ); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index be46d20..937ab9a 100644 --- a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -363,10 +363,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("Id", "CartId"); - b1.HasIndex("ProductId"); + b1.HasIndex("CartId"); - b1.HasIndex("CartId", "ProductId") - .IsUnique(); + b1.HasIndex("ProductId"); b1.ToTable("CartItems", (string)null); diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/RemoveProductFromCartCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/RemoveProductFromCartCommandUtils.cs index 398f749..33a4ebc 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/RemoveProductFromCartCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/RemoveProductFromCartCommandUtils.cs @@ -10,7 +10,7 @@ public static RemoveProductFromCartCommand CreateCommand() return new RemoveProductFromCartCommand( Constants.User.Id.Value, Constants.Cart.Id.Value, - Constants.Product.Id.Value + Constants.CartItem.Id.Value ); } } From 21bdcbd4c3f68445281cca96f95f5fde10a3ae8c Mon Sep 17 00:00:00 2001 From: "Md. Abul Kalam" Date: Fri, 20 Dec 2024 00:09:24 +0600 Subject: [PATCH 4/4] Issue #56: Update login functions --- .../Common/Mapping/paymentMappingConfig.cs | 16 +- .../Controllers/AuthenticationController.cs | 7 +- .../Commands/Register/RegisterCommand.cs | 3 +- .../Register/RegisterCommandHandler.cs | 47 +- .../Queries/login/LoginQueryHandler.cs | 46 +- .../Carts/Queries/GetCart/GetCartQuery.cs | 1 - .../Authentication/IJwtTokenGenerator.cs | 9 +- .../Persistence/IPermissionRepository.cs | 13 + .../Interfaces/Persistence/IUserRepository.cs | 2 +- .../CardNotPresentSaleCommand.cs | 5 +- .../Payment/CardNotPresentSaleRequest.cs | 7 +- src/Shopizy.Domain/Carts/Cart.cs | 1 - src/Shopizy.Domain/Common/Models/Entity.cs | 4 +- src/Shopizy.Domain/Permissions/Permission.cs | 22 + .../Permissions/ValueObjects/PermissionId.cs | 28 + src/Shopizy.Domain/Users/User.cs | 27 +- .../Common/Persistence/AppDbContext.cs | 2 + .../Common/SeedData/SeedData.SQL | 24 + .../DependencyInjectionRegister.cs | 5 +- .../20241212165458_UpdateDatabase.cs | 3 +- ...1219151944_AddPermissionsTable.Designer.cs | 836 ++++++++++++++++++ .../20241219151944_AddPermissionsTable.cs | 68 ++ .../Migrations/AppDbContextModelSnapshot.cs | 42 + .../Persistence/PermissionConfigurations.cs | 26 + .../Persistence/PermissionRepository.cs | 37 + .../CurrentUserProvider/CurrentUser.cs | 9 +- .../CurrentUserProvider.cs | 6 +- .../TokenGenerator/JwtTokenGenerator.cs | 17 +- .../Users/Persistence/UserConfigurations.cs | 25 + .../Users/Persistence/UserRepository.cs | 2 +- .../UpdateProductQuantityCommandUtils.cs | 1 + .../TestUtils/Constants/Constants.User.cs | 3 + .../Users/TestUtils/UserFactory.cs | 3 +- .../TokenGenerator/JwtTokenGeneratorTests.cs | 23 +- 34 files changed, 1227 insertions(+), 143 deletions(-) create mode 100644 src/Shopizy.Application/Common/Interfaces/Persistence/IPermissionRepository.cs create mode 100644 src/Shopizy.Domain/Permissions/Permission.cs create mode 100644 src/Shopizy.Domain/Permissions/ValueObjects/PermissionId.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.Designer.cs create mode 100644 src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.cs create mode 100644 src/Shopizy.Infrastructure/Permissions/Persistence/PermissionConfigurations.cs create mode 100644 src/Shopizy.Infrastructure/Permissions/Persistence/PermissionRepository.cs diff --git a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs index f17b5f8..77bd995 100644 --- a/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs +++ b/src/Shopizy.Api/Common/Mapping/paymentMappingConfig.cs @@ -1,6 +1,5 @@ using Ardalis.GuardClauses; using Mapster; -using Shopizy.Application.Common.models; using Shopizy.Application.Payments.Commands.CardNotPresentSale; using Shopizy.Application.Payments.Commands.CashOnDeliverySale; using Shopizy.Contracts.Payment; @@ -19,7 +18,15 @@ public void Register(TypeAdapterConfig config) CardNotPresentSaleCommand >() .Map(dest => dest.UserId, src => src.UserId) - .Map(dest => dest, src => src.request); + .Map(dest => dest.OrderId, src => src.request.OrderId) + .Map(dest => dest.Amount, src => src.request.Amount) + .Map(dest => dest.Currency, src => src.request.Currency) + .Map(dest => dest.PaymentMethod, src => src.request.PaymentMethod) + .Map(dest => dest.PaymentMethodId, src => src.request.PaymentMethodId) + .Map(dest => dest.CardName, src => src.request.CardInfo.CardName) + .Map(dest => dest.CardExpiryMonth, src => src.request.CardInfo.CardExpiryMonth) + .Map(dest => dest.CardExpiryYear, src => src.request.CardInfo.CardExpiryYear) + .Map(dest => dest.LastDigits, src => src.request.CardInfo.LastDigits); config .NewConfig< @@ -27,6 +34,9 @@ public void Register(TypeAdapterConfig config) CashOnDeliverySaleCommand >() .Map(dest => dest.UserId, src => src.UserId) - .Map(dest => dest, src => src.request); + .Map(dest => dest.OrderId, src => src.request.OrderId) + .Map(dest => dest.Amount, src => src.request.Amount) + .Map(dest => dest.Currency, src => src.request.Currency) + .Map(dest => dest.PaymentMethod, src => src.request.PaymentMethod); } } diff --git a/src/Shopizy.Api/Controllers/AuthenticationController.cs b/src/Shopizy.Api/Controllers/AuthenticationController.cs index 8cd8854..56bf72b 100644 --- a/src/Shopizy.Api/Controllers/AuthenticationController.cs +++ b/src/Shopizy.Api/Controllers/AuthenticationController.cs @@ -24,9 +24,12 @@ public class AuthenticationController(ISender mediator, IMapper mapper) : ApiCon public async Task RegisterAsync(RegisterRequest request) { var command = _mapper.Map(request); - var authResult = await _mediator.Send(command); + var result = await _mediator.Send(command); - return authResult.Match(authResult => Ok(_mapper.Map(authResult)), Problem); + return result.Match( + success => Ok(SuccessResult.Success("Your account has been added. Please log in.")), + Problem + ); } [HttpPost("login")] diff --git a/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommand.cs b/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommand.cs index bd8295b..a2738fb 100644 --- a/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommand.cs +++ b/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommand.cs @@ -1,8 +1,7 @@ using ErrorOr; using MediatR; -using Shopizy.Application.Authentication.Common; namespace Shopizy.Application.Authentication.Commands.Register; public record RegisterCommand(string FirstName, string LastName, string Phone, string Password) - : IRequest>; + : IRequest>; diff --git a/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommandHandler.cs b/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommandHandler.cs index 8c36617..cba8d4c 100644 --- a/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommandHandler.cs +++ b/src/Shopizy.Application/Authentication/Commands/Register/RegisterCommandHandler.cs @@ -1,36 +1,55 @@ using ErrorOr; using MediatR; -using Shopizy.Application.Authentication.Common; using Shopizy.Application.Common.Interfaces.Authentication; using Shopizy.Application.Common.Interfaces.Persistence; using Shopizy.Domain.Common.CustomErrors; +using Shopizy.Domain.Permissions.ValueObjects; using Shopizy.Domain.Users; namespace Shopizy.Application.Authentication.Commands.Register; public class RegisterCommandHandler( IUserRepository userRepository, - IJwtTokenGenerator jwtTokenGenerator, IPasswordManager passwordManager -) : IRequestHandler> +) : IRequestHandler> { private readonly IUserRepository _userRepository = userRepository; - private readonly IJwtTokenGenerator _jwtTokenGenerator = jwtTokenGenerator; private readonly IPasswordManager _passwordManager = passwordManager; - public async Task> Handle( + public async Task> Handle( RegisterCommand command, CancellationToken cancellationToken ) { - if ((await _userRepository.GetUserByPhone(command.Phone)) is not null) + if ((await _userRepository.GetUserByPhoneAsync(command.Phone)) is not null) { return CustomErrors.User.DuplicatePhone; } var hashedPassword = _passwordManager.CreateHashString(command.Password); - var user = User.Create(command.FirstName, command.LastName, command.Phone, hashedPassword); + var permissionIds = new List() + { + PermissionId.Create(new("249E733D-5BDC-49C3-91CA-06AE25A9C897")), + PermissionId.Create(new("D6C2E3C6-314B-4F2E-A407-34139B145771")), + PermissionId.Create(new("9601BA5E-EB54-4487-BFE0-563462D3CC25")), + PermissionId.Create(new("0374E597-604E-4146-8F40-8C994D26C290")), + PermissionId.Create(new("ACD9D507-AC45-4CD2-B0F4-91126C71319A")), + PermissionId.Create(new("2A19090A-B3F3-4B30-9CED-934EE0503D26")), + PermissionId.Create(new("4B88CB16-0228-4669-BA7F-B75F42A3B7AF")), + PermissionId.Create(new("5E2A486B-D9A0-4F83-8FF2-C56EF97CE485")), + PermissionId.Create(new("DD25381D-063C-4A3A-9539-DEEC640919A4")), + PermissionId.Create(new("20082930-3857-4B34-80D0-E256B9B585D8")), + PermissionId.Create(new("0C65A58A-D472-4D5D-848E-EAC46F988F5D")), + }; + + var user = User.Create( + command.FirstName, + command.LastName, + command.Phone, + hashedPassword, + permissionIds + ); await _userRepository.AddAsync(user); @@ -39,18 +58,6 @@ CancellationToken cancellationToken return CustomErrors.User.UserNotCreated; } - var roles = new List(); - var permissions = new List(); - - var token = _jwtTokenGenerator.GenerateToken( - user.Id, - command.FirstName, - command.LastName, - command.Phone, - roles, - permissions - ); - - return new AuthResult(user.Id.Value, user.FirstName, user.LastName, user.Phone, token); + return Result.Success; } } diff --git a/src/Shopizy.Application/Authentication/Queries/login/LoginQueryHandler.cs b/src/Shopizy.Application/Authentication/Queries/login/LoginQueryHandler.cs index 5b1dfdb..c0c27f0 100644 --- a/src/Shopizy.Application/Authentication/Queries/login/LoginQueryHandler.cs +++ b/src/Shopizy.Application/Authentication/Queries/login/LoginQueryHandler.cs @@ -3,19 +3,19 @@ using Shopizy.Application.Authentication.Common; using Shopizy.Application.Common.Interfaces.Authentication; using Shopizy.Application.Common.Interfaces.Persistence; -using Shopizy.Application.Common.Security.Permissions; -using Shopizy.Application.Common.Security.Roles; using Shopizy.Domain.Common.CustomErrors; namespace Shopizy.Application.Authentication.Queries.login; public class LoginQueryHandler( IUserRepository userRepository, + IPermissionRepository permissionRepository, IJwtTokenGenerator jwtTokenGenerator, IPasswordManager passwordManager ) : IRequestHandler> { private readonly IUserRepository _userRepository = userRepository; + private readonly IPermissionRepository _permissionRepository = permissionRepository; private readonly IJwtTokenGenerator _jwtTokenGenerator = jwtTokenGenerator; private readonly IPasswordManager _passwordManager = passwordManager; @@ -24,7 +24,7 @@ public async Task> Handle( CancellationToken cancellationToken ) { - var user = await _userRepository.GetUserByPhone(query.Phone); + var user = await _userRepository.GetUserByPhoneAsync(query.Phone); if (user is null) { return CustomErrors.User.UserNotFoundWhileLogin; @@ -35,39 +35,15 @@ CancellationToken cancellationToken return CustomErrors.Authentication.InvalidCredentials; } - var roles = new List() { Role.Admin }; - var permissions = new List() - { - Permissions.Category.Create, - Permissions.Category.Get, - Permissions.Category.Modify, - Permissions.Category.Delete, - Permissions.Product.Create, - Permissions.Product.Get, - Permissions.Product.Modify, - Permissions.Product.Delete, - Permissions.Cart.Create, - Permissions.Cart.Get, - Permissions.Cart.Modify, - Permissions.Cart.Delete, - Permissions.Order.Create, - Permissions.Order.Get, - Permissions.Order.Modify, - Permissions.Order.Delete, - Permissions.User.Modify, - Permissions.User.Get, - Permissions.User.Create, - Permissions.User.Delete, - }; + var allPermissions = await _permissionRepository.GetAsync(); + + var assignedPermissions = allPermissions + .Where(permission => user.PermissionIds.Contains(permission.Id)) + .Select(p => p.Name); + + var roles = new List() { }; - var token = _jwtTokenGenerator.GenerateToken( - user.Id, - user.FirstName, - user.LastName, - user.Phone, - roles, - permissions - ); + var token = _jwtTokenGenerator.GenerateToken(user.Id, roles, assignedPermissions); return new AuthResult(user.Id.Value, user.FirstName, user.LastName, user.Phone, token); } diff --git a/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs b/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs index fc72d64..9d61f07 100644 --- a/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs +++ b/src/Shopizy.Application/Carts/Queries/GetCart/GetCartQuery.cs @@ -1,5 +1,4 @@ using ErrorOr; -using MediatR; using Shopizy.Application.Common.Security.Permissions; using Shopizy.Application.Common.Security.Policies; using Shopizy.Application.Common.Security.Request; diff --git a/src/Shopizy.Application/Common/Interfaces/Authentication/IJwtTokenGenerator.cs b/src/Shopizy.Application/Common/Interfaces/Authentication/IJwtTokenGenerator.cs index 6b7cee3..fc53d62 100644 --- a/src/Shopizy.Application/Common/Interfaces/Authentication/IJwtTokenGenerator.cs +++ b/src/Shopizy.Application/Common/Interfaces/Authentication/IJwtTokenGenerator.cs @@ -4,12 +4,5 @@ namespace Shopizy.Application.Common.Interfaces.Authentication; public interface IJwtTokenGenerator { - string GenerateToken( - UserId userId, - string firstName, - string LastName, - string phone, - IList roles, - IList Permissions - ); + string GenerateToken(UserId userId, IList roles, IEnumerable Permissions); } diff --git a/src/Shopizy.Application/Common/Interfaces/Persistence/IPermissionRepository.cs b/src/Shopizy.Application/Common/Interfaces/Persistence/IPermissionRepository.cs new file mode 100644 index 0000000..e3b9b42 --- /dev/null +++ b/src/Shopizy.Application/Common/Interfaces/Persistence/IPermissionRepository.cs @@ -0,0 +1,13 @@ +using Shopizy.Domain.Permissions; +using Shopizy.Domain.Permissions.ValueObjects; + +namespace Shopizy.Application.Common.Interfaces.Persistence; + +public interface IPermissionRepository +{ + Task> GetAsync(); + Task GetById(PermissionId id); + Task AddAsync(Permission user); + void Update(Permission user); + Task Commit(CancellationToken cancellationToken); +} diff --git a/src/Shopizy.Application/Common/Interfaces/Persistence/IUserRepository.cs b/src/Shopizy.Application/Common/Interfaces/Persistence/IUserRepository.cs index 7437dcf..3ec434c 100644 --- a/src/Shopizy.Application/Common/Interfaces/Persistence/IUserRepository.cs +++ b/src/Shopizy.Application/Common/Interfaces/Persistence/IUserRepository.cs @@ -5,7 +5,7 @@ namespace Shopizy.Application.Common.Interfaces.Persistence; public interface IUserRepository { - Task GetUserByPhone(string phone); + Task GetUserByPhoneAsync(string phone); Task GetUserById(UserId id); Task AddAsync(User user); void Update(User user); diff --git a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs index fbb7aed..a421fb3 100644 --- a/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs +++ b/src/Shopizy.Application/Payments/Commands/CardNotPresentSale/CardNotPresentSaleCommand.cs @@ -1,5 +1,4 @@ using ErrorOr; -using Shopizy.Application.Common.models; using Shopizy.Application.Common.Security.Permissions; using Shopizy.Application.Common.Security.Policies; using Shopizy.Application.Common.Security.Request; @@ -15,7 +14,7 @@ public record CardNotPresentSaleCommand( string PaymentMethod, string PaymentMethodId, string CardName, - string CardExpiryMonth, - string CardExpiryYear, + int CardExpiryMonth, + int CardExpiryYear, string LastDigits ) : IAuthorizeableRequest>; diff --git a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs index bf23934..c1de299 100644 --- a/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs +++ b/src/Shopizy.Contracts/Payment/CardNotPresentSaleRequest.cs @@ -9,9 +9,4 @@ public record CardNotPresentSaleRequest( CardInfo? CardInfo ); -public record CardInfo( - string CardName, - string CardExpiryMonth, - string CardExpiryYear, - string LastDigits -); +public record CardInfo(string CardName, int CardExpiryMonth, int CardExpiryYear, string LastDigits); diff --git a/src/Shopizy.Domain/Carts/Cart.cs b/src/Shopizy.Domain/Carts/Cart.cs index e856cac..a9766ee 100644 --- a/src/Shopizy.Domain/Carts/Cart.cs +++ b/src/Shopizy.Domain/Carts/Cart.cs @@ -1,7 +1,6 @@ using Shopizy.Domain.Carts.Entities; using Shopizy.Domain.Carts.ValueObjects; using Shopizy.Domain.Common.Models; -using Shopizy.Domain.Products.ValueObjects; using Shopizy.Domain.Users.ValueObjects; namespace Shopizy.Domain.Carts; diff --git a/src/Shopizy.Domain/Common/Models/Entity.cs b/src/Shopizy.Domain/Common/Models/Entity.cs index d69461d..6de87d2 100644 --- a/src/Shopizy.Domain/Common/Models/Entity.cs +++ b/src/Shopizy.Domain/Common/Models/Entity.cs @@ -1,6 +1,7 @@ namespace Shopizy.Domain.Common.Models; -public abstract class Entity : IEquatable>, IHasDomainEvents where TId : notnull +public abstract class Entity : IEquatable>, IHasDomainEvents + where TId : notnull { private readonly List _domainEvents = []; public TId Id { get; protected set; } @@ -50,5 +51,4 @@ public List PopDomainEvents() return copy; } - } diff --git a/src/Shopizy.Domain/Permissions/Permission.cs b/src/Shopizy.Domain/Permissions/Permission.cs new file mode 100644 index 0000000..a5f4d42 --- /dev/null +++ b/src/Shopizy.Domain/Permissions/Permission.cs @@ -0,0 +1,22 @@ +using Shopizy.Domain.Common.Models; +using Shopizy.Domain.Permissions.ValueObjects; + +namespace Shopizy.Domain.Permissions; + +public class Permission : AggregateRoot +{ + public string Name { get; private set; } + + public static Permission Create(string name) + { + return new Permission(PermissionId.CreateUnique(), name); + } + + private Permission(PermissionId permissionId, string name) + : base(permissionId) + { + Name = name; + } + + private Permission() { } +} diff --git a/src/Shopizy.Domain/Permissions/ValueObjects/PermissionId.cs b/src/Shopizy.Domain/Permissions/ValueObjects/PermissionId.cs new file mode 100644 index 0000000..d29bef4 --- /dev/null +++ b/src/Shopizy.Domain/Permissions/ValueObjects/PermissionId.cs @@ -0,0 +1,28 @@ +using Shopizy.Domain.Common.Models; + +namespace Shopizy.Domain.Permissions.ValueObjects; + +public sealed class PermissionId : AggregateRootId +{ + public override Guid Value { get; protected set; } + + private PermissionId(Guid value) + { + Value = value; + } + + public static PermissionId CreateUnique() + { + return new(Guid.NewGuid()); + } + + public static PermissionId Create(Guid value) + { + return new(value); + } + + public override IEnumerable GetEqualityComponents() + { + yield return Value; + } +} diff --git a/src/Shopizy.Domain/Users/User.cs b/src/Shopizy.Domain/Users/User.cs index f547f8f..b2266bf 100644 --- a/src/Shopizy.Domain/Users/User.cs +++ b/src/Shopizy.Domain/Users/User.cs @@ -1,6 +1,7 @@ using Shopizy.Domain.Common.Models; using Shopizy.Domain.Orders; using Shopizy.Domain.Orders.ValueObjects; +using Shopizy.Domain.Permissions.ValueObjects; using Shopizy.Domain.ProductReviews; using Shopizy.Domain.Users.ValueObjects; @@ -8,8 +9,9 @@ namespace Shopizy.Domain.Users; public sealed class User : AggregateRoot { - private readonly List _orders = []; - private readonly List _productReviews = []; + private readonly IList _orders = []; + private readonly IList _productReviews = []; + private readonly IList _permissionIds = []; public string FirstName { get; private set; } public string LastName { get; private set; } @@ -24,21 +26,36 @@ public sealed class User : AggregateRoot public IReadOnlyList Orders => _orders.AsReadOnly(); public IReadOnlyList ProductReviews => _productReviews.AsReadOnly(); + public IReadOnlyList PermissionIds => _permissionIds.AsReadOnly(); - public static User Create(string firstName, string lastName, string phone, string? password) + public static User Create( + string firstName, + string lastName, + string phone, + string? password, + IList permissionIds + ) { - return new(UserId.CreateUnique(), firstName, lastName, phone, password); + return new(UserId.CreateUnique(), firstName, lastName, phone, password, permissionIds); } private User() { } - private User(UserId userId, string firstName, string lastName, string phone, string? password) + private User( + UserId userId, + string firstName, + string lastName, + string phone, + string? password, + IList permissionIds + ) : base(userId) { FirstName = firstName; LastName = lastName; Phone = phone; Password = password; + _permissionIds = permissionIds.ToList(); CreatedOn = DateTime.UtcNow; } diff --git a/src/Shopizy.Infrastructure/Common/Persistence/AppDbContext.cs b/src/Shopizy.Infrastructure/Common/Persistence/AppDbContext.cs index 78d7b46..97d6b86 100644 --- a/src/Shopizy.Infrastructure/Common/Persistence/AppDbContext.cs +++ b/src/Shopizy.Infrastructure/Common/Persistence/AppDbContext.cs @@ -7,6 +7,7 @@ using Shopizy.Domain.Common.Models; using Shopizy.Domain.Orders; using Shopizy.Domain.Payments; +using Shopizy.Domain.Permissions; using Shopizy.Domain.ProductReviews; using Shopizy.Domain.Products; using Shopizy.Domain.PromoCodes; @@ -29,6 +30,7 @@ IPublisher _publisher public DbSet ProductReviews { get; set; } public DbSet PromoCodes { get; set; } public DbSet Users { get; set; } + public DbSet Permissions { get; set; } public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { diff --git a/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL b/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL index 292b5ba..aa3b3c8 100644 --- a/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL +++ b/src/Shopizy.Infrastructure/Common/SeedData/SeedData.SQL @@ -58,3 +58,27 @@ VALUES('20AB2C8F-E880-45C3-8887-D0A0E2E0E763','E68D8E76-72A1-42ED-91D0-0ED0296D6 ('41B53394-5014-45E6-9540-B7FA05782B6C','6EBA643C-D138-4D90-AA7C-6DBF935D8209','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',4, 'Good T-Shirt!', GETDATE()), ('F9CA8F97-2B91-4EAD-88D7-78496A92616A','88D9AF82-BEF4-4042-8E55-B01B89F23E68','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',5, 'Recommanded!', GETDATE()), ('7ED64773-2ED6-4BFB-BBA0-E267BA4A8208','E68D8E76-72A1-42ED-91D0-0ED0296D662E','DF22BA6B-A15B-4125-AF29-07E242AF6F2D',4, 'Looks good', GETDATE()); + + + +INSERT INTO dbo.[Permissions] VALUES +('249E733D-5BDC-49C3-91CA-06AE25A9C897', 'create:cart'), +('4B88CB16-0228-4669-BA7F-B75F42A3B7AF', 'get:cart'), +('20082930-3857-4B34-80D0-E256B9B585D8', 'modify:cart'), +('D6C2E3C6-314B-4F2E-A407-34139B145771', 'delete:cart'), +('F49BBC15-AA8B-4752-AF66-E3E00AFC173D', 'create:category'), +('5E2A486B-D9A0-4F83-8FF2-C56EF97CE485', 'get:category'), +('626DA392-0BBF-4C3F-8909-A8FC18F4DC43', 'modify:category'), +('6811001E-28AE-4BB1-BBB8-99BE33A21302', 'delete:category'), +('2A19090A-B3F3-4B30-9CED-934EE0503D26', 'create:order'), +('9601BA5E-EB54-4487-BFE0-563462D3CC25', 'get:order'), +('ACD9D507-AC45-4CD2-B0F4-91126C71319A', 'modify:order'), +('DD25381D-063C-4A3A-9539-DEEC640919A4', 'delete:order'), +('1DD03229-B8DA-4926-8E4A-12B27A0FF5E7', 'create:product'), +('0C65A58A-D472-4D5D-848E-EAC46F988F5D', 'get:product'), +('43B3188D-6E85-479A-9FD7-0186FCA97F52', 'modify:product'), +('1679BA61-9B46-457E-9974-F02300B9A1D5', 'delete:product'), +('0529A2F2-7507-4FA5-9DAF-68829F9D7FC4', 'create:user'), +('0374E597-604E-4146-8F40-8C994D26C290', 'get:user'), +('C920A577-1669-4167-B056-5E0A03329C55', 'modify:user'), +('80366E1A-634D-4579-9245-164166E1146B', 'delete:user'); diff --git a/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs b/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs index fd6a0c4..23359b8 100644 --- a/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs +++ b/src/Shopizy.Infrastructure/DependencyInjectionRegister.cs @@ -13,6 +13,7 @@ using Shopizy.Infrastructure.ExternalServices.MediaUploader.CloudinaryService; using Shopizy.Infrastructure.ExternalServices.PaymentGateway.Stripe; using Shopizy.Infrastructure.Orders.Persistence; +using Shopizy.Infrastructure.Permissions.Persistence; using Shopizy.Infrastructure.ProductReviews.Persistence; using shopizy.Infrastructure.Products.Persistence; using Shopizy.Infrastructure.PromoCodes.Persistence; @@ -25,7 +26,6 @@ using Shopizy.Infrastructure.Services; using Shopizy.Infrastructure.Users.Persistence; using Stripe; -using Stripe.Checkout; namespace Shopizy.Infrastructure; @@ -159,7 +159,8 @@ private static IServiceCollection AddRepositories(this IServiceCollection servic .AddScoped() .AddScoped() .AddScoped() - .AddScoped(); + .AddScoped() + .AddScoped(); return services; } diff --git a/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs index 97c3512..0321948 100644 --- a/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs +++ b/src/Shopizy.Infrastructure/Migrations/20241212165458_UpdateDatabase.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.Designer.cs b/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.Designer.cs new file mode 100644 index 0000000..a19ad3e --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.Designer.cs @@ -0,0 +1,836 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Shopizy.Infrastructure.Common.Persistence; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241219151944_AddPermissionsTable")] + partial class AddPermissionsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Carts", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Categories", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CancellationReason") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("DeliveryMethod") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderStatus") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("PromoCode") + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("PaymentMethod") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PaymentMethodId") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasMaxLength(260) + .HasColumnType("nvarchar(260)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("UserId"); + + b.ToTable("Payments", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Permissions.Permission", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Permissions", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("ProductReviews", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Barcode") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Brand") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier"); + + b.Property("Colors") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("Favourites") + .HasColumnType("int"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("SKU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ShortDescription") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Sizes") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockQuantity") + .HasColumnType("int"); + + b.Property("Tags") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Products", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.PromoCodes.PromoCode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsPerchantage") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("NumOfTimeUsed") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.ToTable("PromoCodes", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedOn") + .HasColumnType("smalldatetime"); + + b.Property("CustomerId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ModifiedOn") + .HasColumnType("smalldatetime"); + + b.Property("Password") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("nvarchar(15)"); + + b.Property("ProfileImageUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Phone"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Shopizy.Domain.Carts.Cart", b => + { + b.OwnsMany("Shopizy.Domain.Carts.Entities.CartItem", "CartItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("CartId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "CartId"); + + b1.HasIndex("CartId"); + + b1.HasIndex("ProductId"); + + b1.ToTable("CartItems", (string)null); + + b1.WithOwner() + .HasForeignKey("CartId"); + + b1.HasOne("Shopizy.Domain.Products.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b1.Navigation("Product"); + }); + + b.Navigation("CartItems"); + }); + + modelBuilder.Entity("Shopizy.Domain.Orders.Order", b => + { + b.HasOne("Shopizy.Domain.Users.User", null) + .WithMany("Orders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Shopizy.Domain.Orders.Entities.OrderItem", "OrderItems", b1 => + { + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Color") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("Discount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("PictureUrl") + .HasColumnType("nvarchar(max)"); + + b1.Property("Quantity") + .HasColumnType("int"); + + b1.Property("Size") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("Id", "OrderId"); + + b1.HasIndex("OrderId"); + + b1.ToTable("OrderItems", (string)null); + + b1.WithOwner() + .HasForeignKey("OrderId"); + + b1.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b2 => + { + b2.Property("OrderItemId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OrderItemOrderId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b2.Property("Currency") + .HasColumnType("int"); + + b2.HasKey("OrderItemId", "OrderItemOrderId"); + + b2.ToTable("OrderItems"); + + b2.WithOwner() + .HasForeignKey("OrderItemId", "OrderItemOrderId"); + }); + + b1.Navigation("UnitPrice") + .IsRequired(); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "DeliveryCharge", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "ShippingAddress", b1 => + { + b1.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("OrderId"); + + b1.ToTable("Orders"); + + b1.WithOwner() + .HasForeignKey("OrderId"); + }); + + b.Navigation("DeliveryCharge") + .IsRequired(); + + b.Navigation("OrderItems"); + + b.Navigation("ShippingAddress") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Payments.Payment", b => + { + b.HasOne("Shopizy.Domain.Orders.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "BillingAddress", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "Total", b1 => + { + b1.Property("PaymentId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("PaymentId"); + + b1.ToTable("Payments"); + + b1.WithOwner() + .HasForeignKey("PaymentId"); + }); + + b.Navigation("BillingAddress") + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Total") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => + { + b.HasOne("Shopizy.Domain.Products.Product", null) + .WithMany("ProductReviews") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Shopizy.Domain.Users.User", "User") + .WithMany("ProductReviews") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Rating", "Rating", b1 => + { + b1.Property("ProductReviewId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductReviewId"); + + b1.ToTable("ProductReviews"); + + b1.WithOwner() + .HasForeignKey("ProductReviewId"); + }); + + b.Navigation("Rating") + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.HasOne("Shopizy.Domain.Categories.Category", null) + .WithMany("Products") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.AverageRating", "AverageRating", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("NumRatings") + .HasColumnType("int"); + + b1.Property("Value") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsMany("Shopizy.Domain.Products.Entities.ProductImage", "ProductImages", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .HasColumnType("uniqueidentifier"); + + b1.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.Property("PublicId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("Seq") + .HasColumnType("int"); + + b1.HasKey("ProductId", "Id"); + + b1.ToTable("ProductImages", (string)null); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.OwnsOne("Shopizy.Domain.Common.ValueObjects.Price", "UnitPrice", b1 => + { + b1.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)"); + + b1.Property("Currency") + .HasColumnType("int"); + + b1.HasKey("ProductId"); + + b1.ToTable("Products"); + + b1.WithOwner() + .HasForeignKey("ProductId"); + }); + + b.Navigation("AverageRating") + .IsRequired(); + + b.Navigation("ProductImages"); + + b.Navigation("UnitPrice") + .IsRequired(); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.OwnsMany("Shopizy.Domain.Permissions.ValueObjects.PermissionId", "PermissionIds", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasColumnType("uniqueidentifier") + .HasColumnName("PermissionId"); + + b1.HasKey("Id"); + + b1.HasIndex("UserId"); + + b1.ToTable("UserPermissionIds", (string)null); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => + { + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("City") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Country") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("State") + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); + + b1.Property("Street") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b1.Property("ZipCode") + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("Address"); + + b.Navigation("PermissionIds"); + }); + + modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => + { + b.Navigation("Products"); + }); + + modelBuilder.Entity("Shopizy.Domain.Products.Product", b => + { + b.Navigation("ProductReviews"); + }); + + modelBuilder.Entity("Shopizy.Domain.Users.User", b => + { + b.Navigation("Orders"); + + b.Navigation("ProductReviews"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.cs b/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.cs new file mode 100644 index 0000000..b0b313c --- /dev/null +++ b/src/Shopizy.Infrastructure/Migrations/20241219151944_AddPermissionsTable.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace shopizy.Infrastructure.Migrations +{ + /// + public partial class AddPermissionsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Permissions", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column( + type: "nvarchar(50)", + maxLength: 50, + nullable: false + ), + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.Id); + } + ); + + migrationBuilder.CreateTable( + name: "UserPermissionIds", + columns: table => new + { + Id = table + .Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PermissionId = table.Column(type: "uniqueidentifier", nullable: false), + UserId = table.Column(type: "uniqueidentifier", nullable: false), + }, + constraints: table => + { + table.PrimaryKey("PK_UserPermissionIds", x => x.Id); + table.ForeignKey( + name: "FK_UserPermissionIds_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade + ); + } + ); + + migrationBuilder.CreateIndex( + name: "IX_UserPermissionIds_UserId", + table: "UserPermissionIds", + column: "UserId" + ); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable(name: "Permissions"); + + migrationBuilder.DropTable(name: "UserPermissionIds"); + } + } +} diff --git a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 937ab9a..3e735ce 100644 --- a/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Shopizy.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -142,6 +142,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Payments", (string)null); }); + modelBuilder.Entity("Shopizy.Domain.Permissions.Permission", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Permissions", (string)null); + }); + modelBuilder.Entity("Shopizy.Domain.ProductReviews.ProductReview", b => { b.Property("Id") @@ -733,6 +748,31 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Shopizy.Domain.Users.User", b => { + b.OwnsMany("Shopizy.Domain.Permissions.ValueObjects.PermissionId", "PermissionIds", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Value") + .HasColumnType("uniqueidentifier") + .HasColumnName("PermissionId"); + + b1.HasKey("Id"); + + b1.HasIndex("UserId"); + + b1.ToTable("UserPermissionIds", (string)null); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + b.OwnsOne("Shopizy.Domain.Orders.ValueObjects.Address", "Address", b1 => { b1.Property("UserId") @@ -767,6 +807,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); b.Navigation("Address"); + + b.Navigation("PermissionIds"); }); modelBuilder.Entity("Shopizy.Domain.Categories.Category", b => diff --git a/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionConfigurations.cs b/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionConfigurations.cs new file mode 100644 index 0000000..5931d8a --- /dev/null +++ b/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionConfigurations.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Shopizy.Domain.Permissions; +using Shopizy.Domain.Permissions.ValueObjects; + +namespace Shopizy.Infrastructure.Permissions.Persistence; + +public sealed class PermissionConfigurations : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + ConfigureUsersTable(builder); + } + + private static void ConfigureUsersTable(EntityTypeBuilder builder) + { + builder.ToTable("Permissions").HasKey(p => p.Id); + + builder + .Property(p => p.Id) + .ValueGeneratedNever() + .HasConversion(id => id.Value, value => PermissionId.Create(value)); + + builder.Property(u => u.Name).HasMaxLength(50); + } +} diff --git a/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionRepository.cs b/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionRepository.cs new file mode 100644 index 0000000..8ca3352 --- /dev/null +++ b/src/Shopizy.Infrastructure/Permissions/Persistence/PermissionRepository.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Shopizy.Application.Common.Interfaces.Persistence; +using Shopizy.Domain.Permissions; +using Shopizy.Domain.Permissions.ValueObjects; +using Shopizy.Infrastructure.Common.Persistence; + +namespace Shopizy.Infrastructure.Permissions.Persistence; + +public class PermissionRepository(AppDbContext dbContext) : IPermissionRepository +{ + private readonly AppDbContext _dbContext = dbContext; + + public Task> GetAsync() + { + return _dbContext.Permissions.ToListAsync(); + } + + public Task GetById(PermissionId id) + { + return _dbContext.Permissions.SingleOrDefaultAsync(u => u.Id == id); + } + + public async Task AddAsync(Permission user) + { + await _dbContext.Permissions.AddAsync(user); + } + + public void Update(Permission user) + { + _dbContext.Permissions.Update(user); + } + + public Task Commit(CancellationToken cancellationToken) + { + return _dbContext.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUser.cs b/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUser.cs index 01a532e..b36cc6c 100644 --- a/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUser.cs +++ b/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUser.cs @@ -1,10 +1,3 @@ namespace Shopizy.Infrastructure.Security.CurrentUserProvider; -public record CurrentUser( - Guid Id, - string FirstName, - string LastName, - string Phone, - IReadOnlyList Permissions, - IReadOnlyList Roles -); +public record CurrentUser(Guid Id, IReadOnlyList Permissions, IReadOnlyList Roles); diff --git a/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUserProvider.cs b/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUserProvider.cs index f18aed6..9ff74c6 100644 --- a/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUserProvider.cs +++ b/src/Shopizy.Infrastructure/Security/CurrentUserProvider/CurrentUserProvider.cs @@ -1,4 +1,3 @@ -using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Ardalis.GuardClauses; using Microsoft.AspNetCore.Http; @@ -21,11 +20,8 @@ public class CurrentUserProvider(IHttpContextAccessor httpContextAccessor) : ICu var id = Guid.Parse(GetSingleClaimValue("id")); List permissions = GetClaimValues("permissions"); List roles = GetClaimValues(ClaimTypes.Role); - string firstName = GetSingleClaimValue(JwtRegisteredClaimNames.Name); - string lastName = GetSingleClaimValue(ClaimTypes.Surname); - string phone = GetSingleClaimValue(ClaimTypes.MobilePhone); - return new CurrentUser(id, firstName, lastName, phone, permissions, roles); + return new CurrentUser(id, permissions, roles); } private List GetClaimValues(string claimType) => diff --git a/src/Shopizy.Infrastructure/Security/TokenGenerator/JwtTokenGenerator.cs b/src/Shopizy.Infrastructure/Security/TokenGenerator/JwtTokenGenerator.cs index 4a1ccdf..33579ee 100644 --- a/src/Shopizy.Infrastructure/Security/TokenGenerator/JwtTokenGenerator.cs +++ b/src/Shopizy.Infrastructure/Security/TokenGenerator/JwtTokenGenerator.cs @@ -13,26 +13,13 @@ public class JwtTokenGenerator(IOptions jwtOptoins) : IJwtTokenGene { private readonly JwtSettings _jwtSettings = jwtOptoins.Value; - public string GenerateToken( - UserId userId, - string firstName, - string LastName, - string phone, - IList roles, - IList Permissions - ) + public string GenerateToken(UserId userId, IList roles, IEnumerable Permissions) { Guard.Against.Null(roles); Guard.Against.Null(Permissions); Guard.Against.Null(userId); - var claims = new List - { - new("id", userId.Value.ToString()), - new(JwtRegisteredClaimNames.Name, firstName), - new(ClaimTypes.Surname, LastName), - new(ClaimTypes.MobilePhone, phone), - }; + var claims = new List { new("id", userId.Value.ToString()) }; foreach (string role in roles) { diff --git a/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs b/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs index 8d43d39..7995b5d 100644 --- a/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs +++ b/src/Shopizy.Infrastructure/Users/Persistence/UserConfigurations.cs @@ -10,6 +10,7 @@ public sealed class UserConfigurations : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { ConfigureUsersTable(builder); + ConfigureUserPermissionsTable(builder); } private static void ConfigureUsersTable(EntityTypeBuilder builder) @@ -51,4 +52,28 @@ private static void ConfigureUsersTable(EntityTypeBuilder builder) builder.Navigation(p => p.Orders).UsePropertyAccessMode(PropertyAccessMode.Field); builder.Navigation(p => p.ProductReviews).UsePropertyAccessMode(PropertyAccessMode.Field); } + + private static void ConfigureUserPermissionsTable(EntityTypeBuilder builder) + { + builder.OwnsMany( + u => u.PermissionIds, + permissionBuilder => + { + permissionBuilder.ToTable("UserPermissionIds"); + + permissionBuilder.WithOwner().HasForeignKey("UserId"); + + permissionBuilder.HasKey("Id"); + + permissionBuilder + .Property(d => d.Value) + .HasColumnName("PermissionId") + .ValueGeneratedNever(); + } + ); + + builder + .Metadata.FindNavigation(nameof(User.PermissionIds))! + .SetPropertyAccessMode(PropertyAccessMode.Field); + } } diff --git a/src/Shopizy.Infrastructure/Users/Persistence/UserRepository.cs b/src/Shopizy.Infrastructure/Users/Persistence/UserRepository.cs index 329029c..d3965b5 100644 --- a/src/Shopizy.Infrastructure/Users/Persistence/UserRepository.cs +++ b/src/Shopizy.Infrastructure/Users/Persistence/UserRepository.cs @@ -10,7 +10,7 @@ public class UserRepository(AppDbContext dbContext) : IUserRepository { private readonly AppDbContext _dbContext = dbContext; - public Task GetUserByPhone(string phone) + public Task GetUserByPhoneAsync(string phone) { return _dbContext.Users.SingleOrDefaultAsync(u => u.Phone == phone); } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/UpdateProductQuantityCommandUtils.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/UpdateProductQuantityCommandUtils.cs index 9cd12f7..8cc0419 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/UpdateProductQuantityCommandUtils.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Carts/TestUtils/UpdateProductQuantityCommandUtils.cs @@ -10,6 +10,7 @@ public static UpdateProductQuantityCommand CreateCommand(int quantity) return new UpdateProductQuantityCommand( Constants.User.Id.Value, Constants.Cart.Id.Value, + Constants.CartItem.Id.Value, Constants.Product.Id.Value, quantity ); diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs index a50316b..f3884e7 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/TestUtils/Constants/Constants.User.cs @@ -1,4 +1,5 @@ using Shopizy.Domain.Orders.ValueObjects; +using Shopizy.Domain.Permissions.ValueObjects; using Shopizy.Domain.Users.ValueObjects; namespace Shopizy.Application.UnitTests.TestUtils.Constants; @@ -24,5 +25,7 @@ public static class User "Country", "ZipCode" ); + + public static readonly PermissionId PermissionId = PermissionId.CreateUnique(); } } diff --git a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs index b1faff8..3dc904d 100644 --- a/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs +++ b/tests/UnitTests/Shopizy.Application.UnitTests/Users/TestUtils/UserFactory.cs @@ -11,7 +11,8 @@ public static User CreateUser() Constants.User.FirstName, Constants.User.LastName, Constants.User.Phone, - Constants.User.Password + Constants.User.Password, + [Constants.User.PermissionId] ); } diff --git a/tests/UnitTests/Shopizy.Infrastructure.UnitTests/Security/TokenGenerator/JwtTokenGeneratorTests.cs b/tests/UnitTests/Shopizy.Infrastructure.UnitTests/Security/TokenGenerator/JwtTokenGeneratorTests.cs index 2ff9da3..4d70a3a 100644 --- a/tests/UnitTests/Shopizy.Infrastructure.UnitTests/Security/TokenGenerator/JwtTokenGeneratorTests.cs +++ b/tests/UnitTests/Shopizy.Infrastructure.UnitTests/Security/TokenGenerator/JwtTokenGeneratorTests.cs @@ -1,5 +1,4 @@ using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; using FluentAssertions; using Microsoft.Extensions.Options; using Shopizy.Application.Common.Interfaces.Authentication; @@ -31,34 +30,18 @@ public void ShouldCreateTokenWithCorrectClaimsWhenCalledAsync() { // Arrange var userId = UserId.CreateUnique(); - string firstName = "John"; - string lastName = "Doe"; - string phone = "123456789"; var roles = new List { "Admin", "Moderator" }; var permissions = new List { "CanCreateProduct", "CanEditProduct" }; // Act - string token = _jwtTokenGenerator.GenerateToken( - userId, - firstName, - lastName, - phone, - roles, - permissions - ); + string token = _jwtTokenGenerator.GenerateToken(userId, roles, permissions); // Assert var jwtToken = new JwtSecurityToken(token); - jwtToken.Claims.Should().HaveCount(13); + jwtToken.Claims.Should().HaveCount(10); jwtToken.Claims.Should().Contain(c => c.Type == "id" && c.Value == userId.Value.ToString()); - jwtToken - .Claims.Should() - .Contain(c => c.Type == JwtRegisteredClaimNames.Name && c.Value == firstName); - jwtToken - .Claims.Should() - .Contain(c => c.Type == JwtRegisteredClaimNames.FamilyName && c.Value == lastName); - jwtToken.Claims.Should().Contain(c => c.Type == ClaimTypes.MobilePhone && c.Value == phone); + jwtToken.Claims.Should().Contain(c => c.Type == "role" && c.Value == "Admin"); jwtToken.Claims.Should().Contain(c => c.Type == "role" && c.Value == "Moderator");