From fda7088abbf8c5dbf514e56716c838f30ca043e7 Mon Sep 17 00:00:00 2001 From: "Amir.H Khademi" Date: Sun, 1 Sep 2024 12:28:42 +0330 Subject: [PATCH] feat ( ReviewEntitiy ) - Add review entity and CQRS --- .version | 2 +- Netina.Api/Controllers/ProductController.cs | 7 +++++++ .../Controllers/ProductReviewController.cs | 12 ++++++------ Netina.Api/Netina.Api.csproj | 4 ++-- Netina.Api/Program.cs | 1 + Netina.Core/BaseServices/PageService.cs | 5 ++++- .../ConfirmReviewCommandHandler.cs | 10 ++++++---- .../CommandQueries/Commands/ReviewCommands.cs | 8 ++++---- .../CommandQueries/Queries/ReviewQueries.cs | 2 +- Netina.Domain/Dtos/LargDtos/ReviewLDto.cs | 4 +++- Netina.Domain/Dtos/SmallDtos/ReviewSDto.cs | 4 +++- .../Entities/Products/Product.Aggregate.cs | 9 --------- Netina.Domain/Entities/Products/Product.cs | 2 +- .../Entities/Reviews/Review.Aggregate.cs | 12 ++++++++++++ .../Entities/{Products => Reviews}/Review.cs | 2 +- Netina.Domain/Mappers/ProductMapper.g.cs | 1 + Netina.Domain/Mappers/ReviewMapper.g.cs | 1 + Netina.Domain/Netina.Domain.csproj | 2 +- .../Reviews/CreateReviewCommandHandler.cs | 16 +++++++++++----- .../Reviews/DeleteReviewCommandHandler.cs | 15 ++++++++++++--- .../Handlers/Reviews/GetReviewQueryHandler.cs | 4 +++- .../Handlers/Reviews/GetReviewsQueryHandler.cs | 12 +++++++++--- Netina.Repository/Netina.Repository.csproj | 1 + 23 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 Netina.Domain/Entities/Reviews/Review.Aggregate.cs rename Netina.Domain/Entities/{Products => Reviews}/Review.cs (96%) diff --git a/.version b/.version index 1a0417d..d02979a 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.2.11.13 \ No newline at end of file +1.3.12.16 \ No newline at end of file diff --git a/Netina.Api/Controllers/ProductController.cs b/Netina.Api/Controllers/ProductController.cs index 4b85f29..768b1d0 100644 --- a/Netina.Api/Controllers/ProductController.cs +++ b/Netina.Api/Controllers/ProductController.cs @@ -38,12 +38,19 @@ public class ProductController : ICarterModule .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts)) .HasApiVersion(1.0); + group.MapGet("{productId}/review",GetProductReviewsAsync) + .WithDisplayName("Get Product Reviews") + .HasApiVersion(1.0); + group.MapDelete("{id}", Delete) .WithDisplayName("Delete Product") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageProducts)) .HasApiVersion(1.0); } + private async Task GetProductReviewsAsync([FromQuery] int page, [FromRoute] Guid productId, IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new GetReviewsQuery(page, productId), cancellationToken)); + // GET:Get All Entity public async Task GetAllAsync([FromQuery] int page, [FromQuery] string? productName, diff --git a/Netina.Api/Controllers/ProductReviewController.cs b/Netina.Api/Controllers/ProductReviewController.cs index 7972d29..cd17875 100644 --- a/Netina.Api/Controllers/ProductReviewController.cs +++ b/Netina.Api/Controllers/ProductReviewController.cs @@ -8,27 +8,27 @@ public class ProductReviewController : ICarterModule .MapGroup("product/review"); group.MapGet("{id}", GetAsync) - .WithDisplayName("Get ProductReview") + .WithDisplayName("Get Product Review") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ViewAllReviews,ApplicationPermission.ManageReview)) .HasApiVersion(1.0); group.MapGet("", GetAllAsync) - .WithDisplayName("Get ProductReview") + .WithDisplayName("Get Product Reviews") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ViewAllReviews, ApplicationPermission.ManageReview)) .HasApiVersion(1.0); group.MapPost("", PostAsync) - .WithDisplayName("Create ProductReview") - .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageReview, ApplicationPermission.AddReview)) + .WithDisplayName("Create Product Review") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser()) .HasApiVersion(1.0); group.MapPut("confirm/{id}", ConfirmAsync) - .WithDisplayName("Confirm ProductReview") + .WithDisplayName("Confirm Product Review") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ConfirmReview, ApplicationPermission.ManageReview)) .HasApiVersion(1.0); group.MapDelete("{id}", DeleteAsync) - .WithDisplayName("Delete ProductReview") + .WithDisplayName("Delete Product Review") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageReview)) .HasApiVersion(1.0); } diff --git a/Netina.Api/Netina.Api.csproj b/Netina.Api/Netina.Api.csproj index 291980c..c8d0b93 100644 --- a/Netina.Api/Netina.Api.csproj +++ b/Netina.Api/Netina.Api.csproj @@ -6,8 +6,8 @@ enable true Linux - 1.2.11.13 - 1.2.11.13 + 1.3.12.16 + 1.3.12.16 diff --git a/Netina.Api/Program.cs b/Netina.Api/Program.cs index 3539a43..9101dbc 100644 --- a/Netina.Api/Program.cs +++ b/Netina.Api/Program.cs @@ -7,6 +7,7 @@ builder.Host.UseSerilog(); LoggerConfig.ConfigureSerilog(); string env = builder.Environment.IsDevelopment() == true ? "Development" : "Production"; builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); + if (builder.Environment.IsDevelopment()) { string projectName = "Vesmeh"; diff --git a/Netina.Core/BaseServices/PageService.cs b/Netina.Core/BaseServices/PageService.cs index 85b9c88..a5801f2 100644 --- a/Netina.Core/BaseServices/PageService.cs +++ b/Netina.Core/BaseServices/PageService.cs @@ -104,7 +104,10 @@ public class PageService( { var newLink = pageSetting.RedirectItems.FirstOrDefault(f => f.OldUrl.ToLower().Trim() == oldEncode.ToLower().Trim()); if (newLink != null) - return newLink.NewUrl; + { + var response = newLink.NewUrl[0] == '/' ? newLink.NewUrl : $"/{newLink.NewUrl}"; + return response; + } else throw new BaseApiException(ApiResultStatusCode.NotFound, "Url not found"); } diff --git a/Netina.Core/EntityServices/ReviewHandlers/ConfirmReviewCommandHandler.cs b/Netina.Core/EntityServices/ReviewHandlers/ConfirmReviewCommandHandler.cs index 5930633..871a401 100644 --- a/Netina.Core/EntityServices/ReviewHandlers/ConfirmReviewCommandHandler.cs +++ b/Netina.Core/EntityServices/ReviewHandlers/ConfirmReviewCommandHandler.cs @@ -1,9 +1,11 @@ -namespace Netina.Core.EntityServices.ReviewHandlers; +using Review = Netina.Domain.Entities.Reviews.Review; + +namespace Netina.Core.EntityServices.ReviewHandlers; public class ConfirmReviewCommandHandler(IRepositoryWrapper repositoryWrapper) - : IRequestHandler + : IRequestHandler { - public async Task Handle(ConfirmReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(ConfirmReviewCommand request, CancellationToken cancellationToken) { var review = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(r => r.Id == request.Id, cancellationToken); @@ -13,6 +15,6 @@ public class ConfirmReviewCommandHandler(IRepositoryWrapper repositoryWrapper) repositoryWrapper.SetRepository().Update(review); await repositoryWrapper.SaveChangesAsync(cancellationToken); - return true; + return review.Id; } } \ No newline at end of file diff --git a/Netina.Domain/CommandQueries/Commands/ReviewCommands.cs b/Netina.Domain/CommandQueries/Commands/ReviewCommands.cs index 0ed24a7..453b72b 100644 --- a/Netina.Domain/CommandQueries/Commands/ReviewCommands.cs +++ b/Netina.Domain/CommandQueries/Commands/ReviewCommands.cs @@ -1,6 +1,6 @@ namespace Netina.Domain.CommandQueries.Commands; -public sealed record CreateReviewCommand(string Title, string Comment, float Rate, bool IsBuyer, Guid ProductId, Guid UserId) : IRequest; -public sealed record UpdateReviewCommand(Guid Id,string Title, string Comment, float Rate, bool IsBuyer, Guid ProductId, Guid UserId): IRequest; -public sealed record ConfirmReviewCommand(Guid Id) : IRequest; -public sealed record DeleteReviewCommand(Guid Id) : IRequest; \ No newline at end of file +public sealed record CreateReviewCommand(string Title, string Comment, float Rate, bool IsBuyer, Guid ProductId, Guid UserId) : IRequest; +public sealed record UpdateReviewCommand(Guid Id,string Title, string Comment, float Rate, bool IsBuyer, Guid ProductId, Guid UserId): IRequest; +public sealed record ConfirmReviewCommand(Guid Id) : IRequest; +public sealed record DeleteReviewCommand(Guid Id) : IRequest; \ No newline at end of file diff --git a/Netina.Domain/CommandQueries/Queries/ReviewQueries.cs b/Netina.Domain/CommandQueries/Queries/ReviewQueries.cs index 58ae809..6dcfd72 100644 --- a/Netina.Domain/CommandQueries/Queries/ReviewQueries.cs +++ b/Netina.Domain/CommandQueries/Queries/ReviewQueries.cs @@ -1,4 +1,4 @@ namespace Netina.Domain.CommandQueries.Queries; -public record GetReviewsQuery(int Page = 0) : IRequest>; +public record GetReviewsQuery(int Page = 0,Guid? ProductId = null) : IRequest>; public record GetReviewQuery(Guid Id) : IRequest; \ No newline at end of file diff --git a/Netina.Domain/Dtos/LargDtos/ReviewLDto.cs b/Netina.Domain/Dtos/LargDtos/ReviewLDto.cs index 5599db5..2d1f587 100644 --- a/Netina.Domain/Dtos/LargDtos/ReviewLDto.cs +++ b/Netina.Domain/Dtos/LargDtos/ReviewLDto.cs @@ -1,4 +1,6 @@ -namespace Netina.Domain.Dtos.LargDtos; +using Review = Netina.Domain.Entities.Reviews.Review; + +namespace Netina.Domain.Dtos.LargDtos; public class ReviewLDto : BaseDto { diff --git a/Netina.Domain/Dtos/SmallDtos/ReviewSDto.cs b/Netina.Domain/Dtos/SmallDtos/ReviewSDto.cs index f3b7cfc..2c4fcda 100644 --- a/Netina.Domain/Dtos/SmallDtos/ReviewSDto.cs +++ b/Netina.Domain/Dtos/SmallDtos/ReviewSDto.cs @@ -1,4 +1,6 @@ -namespace Netina.Domain.Dtos.SmallDtos; +using Review = Netina.Domain.Entities.Reviews.Review; + +namespace Netina.Domain.Dtos.SmallDtos; public class ReviewSDto : BaseDto { diff --git a/Netina.Domain/Entities/Products/Product.Aggregate.cs b/Netina.Domain/Entities/Products/Product.Aggregate.cs index 5344e7c..45d6e96 100644 --- a/Netina.Domain/Entities/Products/Product.Aggregate.cs +++ b/Netina.Domain/Entities/Products/Product.Aggregate.cs @@ -69,16 +69,7 @@ public partial class ProductStorageFile } } -public partial class Review -{ - public static Review Create(string title, string comment, float rate, bool isBuyer, Guid productId, Guid userId) - { - return new Review(title, comment, rate, isBuyer, productId, userId); - } - public void ConfirmReview() - => IsConfirmed = true; -} public partial class Specification { diff --git a/Netina.Domain/Entities/Products/Product.cs b/Netina.Domain/Entities/Products/Product.cs index 6d7ed76..f1fb47e 100644 --- a/Netina.Domain/Entities/Products/Product.cs +++ b/Netina.Domain/Entities/Products/Product.cs @@ -76,7 +76,7 @@ public partial class Product : ApiEntity public ProductCategory? Category { get; internal set; } public List Specifications { get; internal set; } = new(); - public List Reviews { get; internal set; } = new(); + public List Reviews { get; internal set; } = new(); public List Files { get; internal set; } = new(); public List OrderProducts { get; internal set; } = new(); diff --git a/Netina.Domain/Entities/Reviews/Review.Aggregate.cs b/Netina.Domain/Entities/Reviews/Review.Aggregate.cs new file mode 100644 index 0000000..286b14f --- /dev/null +++ b/Netina.Domain/Entities/Reviews/Review.Aggregate.cs @@ -0,0 +1,12 @@ +namespace Netina.Domain.Entities.Reviews; + +public partial class Review +{ + public static Reviews.Review Create(string title, string comment, float rate, bool isBuyer, Guid productId, Guid userId) + { + return new Reviews.Review(title, comment, rate, isBuyer, productId, userId); + } + + public void ConfirmReview() + => IsConfirmed = true; +} \ No newline at end of file diff --git a/Netina.Domain/Entities/Products/Review.cs b/Netina.Domain/Entities/Reviews/Review.cs similarity index 96% rename from Netina.Domain/Entities/Products/Review.cs rename to Netina.Domain/Entities/Reviews/Review.cs index 17ff7f6..cbec549 100644 --- a/Netina.Domain/Entities/Products/Review.cs +++ b/Netina.Domain/Entities/Reviews/Review.cs @@ -1,4 +1,4 @@ -namespace Netina.Domain.Entities.Products; +namespace Netina.Domain.Entities.Reviews; [AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)] [AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)] [AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] diff --git a/Netina.Domain/Mappers/ProductMapper.g.cs b/Netina.Domain/Mappers/ProductMapper.g.cs index 8d3aa26..86b1314 100644 --- a/Netina.Domain/Mappers/ProductMapper.g.cs +++ b/Netina.Domain/Mappers/ProductMapper.g.cs @@ -9,6 +9,7 @@ using Netina.Domain.Dtos.SmallDtos; using Netina.Domain.Entities.Brands; using Netina.Domain.Entities.ProductCategories; using Netina.Domain.Entities.Products; +using Review = Netina.Domain.Entities.Reviews.Review; namespace Netina.Domain.Mappers { diff --git a/Netina.Domain/Mappers/ReviewMapper.g.cs b/Netina.Domain/Mappers/ReviewMapper.g.cs index b0fa32c..fbb915a 100644 --- a/Netina.Domain/Mappers/ReviewMapper.g.cs +++ b/Netina.Domain/Mappers/ReviewMapper.g.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using Netina.Domain.Dtos.LargDtos; using Netina.Domain.Dtos.SmallDtos; using Netina.Domain.Entities.Products; +using Review = Netina.Domain.Entities.Reviews.Review; namespace Netina.Domain.Mappers { diff --git a/Netina.Domain/Netina.Domain.csproj b/Netina.Domain/Netina.Domain.csproj index 7003ad6..8e0cfa8 100644 --- a/Netina.Domain/Netina.Domain.csproj +++ b/Netina.Domain/Netina.Domain.csproj @@ -60,7 +60,7 @@ - + diff --git a/Netina.Repository/Handlers/Reviews/CreateReviewCommandHandler.cs b/Netina.Repository/Handlers/Reviews/CreateReviewCommandHandler.cs index fc565fe..c601fdb 100644 --- a/Netina.Repository/Handlers/Reviews/CreateReviewCommandHandler.cs +++ b/Netina.Repository/Handlers/Reviews/CreateReviewCommandHandler.cs @@ -2,13 +2,19 @@ namespace Netina.Repository.Handlers.Reviews; -public class CreateReviewCommandHandler(IRepositoryWrapper repositoryWrapper) - : IRequestHandler +public class CreateReviewCommandHandler(IRepositoryWrapper repositoryWrapper,ICurrentUserService currentUserService) + : IRequestHandler { - public async Task Handle(CreateReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(CreateReviewCommand request, CancellationToken cancellationToken) { + Guid userId = request.UserId; + if (userId == default) + { + if (!Guid.TryParse(currentUserService.UserId, out userId)) + throw new AppException("User id is wrong", ApiResultStatusCode.BadRequest); + } var review = Review.Create(request.Title, request.Comment, request.Rate, request.IsBuyer, request.ProductId, - request.UserId); + userId); var product = await repositoryWrapper.SetRepository() .TableNoTracking .FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken); @@ -23,6 +29,6 @@ public class CreateReviewCommandHandler(IRepositoryWrapper repositoryWrapper) repositoryWrapper.SetRepository().Add(review); await repositoryWrapper.SaveChangesAsync(cancellationToken); - return review.AdaptToSDto(); + return review.Id; } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Reviews/DeleteReviewCommandHandler.cs b/Netina.Repository/Handlers/Reviews/DeleteReviewCommandHandler.cs index f1932fe..3d2dd43 100644 --- a/Netina.Repository/Handlers/Reviews/DeleteReviewCommandHandler.cs +++ b/Netina.Repository/Handlers/Reviews/DeleteReviewCommandHandler.cs @@ -1,17 +1,26 @@ namespace Netina.Repository.Handlers.Reviews; public class DeleteReviewCommandHandler(IRepositoryWrapper repositoryWrapper) - : IRequestHandler + : IRequestHandler { - public async Task Handle(DeleteReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(DeleteReviewCommand request, CancellationToken cancellationToken) { var review = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(r => r.Id == request.Id, cancellationToken); if (review == null) throw new AppException("Review not found", ApiResultStatusCode.NotFound); + var product = await repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(p => p.Id == review.ProductId, cancellationToken); + if (product != null) + { + product.RemoveRate(review.Rate); + repositoryWrapper.SetRepository().Update(product); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + } repositoryWrapper.SetRepository().Delete(review); await repositoryWrapper.SaveChangesAsync(cancellationToken); - return true; + return review.Id; } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Reviews/GetReviewQueryHandler.cs b/Netina.Repository/Handlers/Reviews/GetReviewQueryHandler.cs index d61d816..3d40833 100644 --- a/Netina.Repository/Handlers/Reviews/GetReviewQueryHandler.cs +++ b/Netina.Repository/Handlers/Reviews/GetReviewQueryHandler.cs @@ -1,4 +1,6 @@ -namespace Netina.Repository.Handlers.Reviews; +using Review = Netina.Domain.Entities.Reviews.Review; + +namespace Netina.Repository.Handlers.Reviews; public class GetReviewQueryHandler(IRepositoryWrapper repositoryWrapper) : IRequestHandler { diff --git a/Netina.Repository/Handlers/Reviews/GetReviewsQueryHandler.cs b/Netina.Repository/Handlers/Reviews/GetReviewsQueryHandler.cs index cf14554..efbeb55 100644 --- a/Netina.Repository/Handlers/Reviews/GetReviewsQueryHandler.cs +++ b/Netina.Repository/Handlers/Reviews/GetReviewsQueryHandler.cs @@ -1,12 +1,18 @@ -namespace Netina.Repository.Handlers.Reviews; +using Review = Netina.Domain.Entities.Reviews.Review; + +namespace Netina.Repository.Handlers.Reviews; public class GetReviewsQueryHandler(IRepositoryWrapper repositoryWrapper) : IRequestHandler> { public async Task> Handle(GetReviewsQuery request, CancellationToken cancellationToken) { - return await repositoryWrapper.SetRepository() - .TableNoTracking + var query = repositoryWrapper.SetRepository() + .TableNoTracking; + if (request.ProductId != null) + query = query.Where(q => q.ProductId == request.ProductId); + + return await query .OrderByDescending(r => r.CreatedAt) .Skip(request.Page * 15) .Take(15) diff --git a/Netina.Repository/Netina.Repository.csproj b/Netina.Repository/Netina.Repository.csproj index d5f6d82..95b22e6 100644 --- a/Netina.Repository/Netina.Repository.csproj +++ b/Netina.Repository/Netina.Repository.csproj @@ -62,6 +62,7 @@ +