-
Notifications
You must be signed in to change notification settings - Fork 1
Add Review Service and Controller #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5eaa2f4
42e8f67
01a49ad
c261a64
83f6039
8555bb5
c37dce7
615d1d3
b2f9eaa
dc74659
5c754b0
4808607
84c8f86
8f2a4c9
9628063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| using AutoMapper; | ||
|
|
||
| namespace Dentizone.Application.AutoMapper.Review | ||
| { | ||
| internal class ReviewProfile : Profile | ||
| { | ||
| public ReviewProfile() | ||
| { | ||
| CreateMap<Dentizone.Domain.Entity.Review, Dentizone.Application.DTOs.Review.CreateReviewDto>().ReverseMap(); | ||
| CreateMap<Dentizone.Domain.Entity.Review, Dentizone.Application.DTOs.Review.UpdateReviewDto>().ReverseMap(); | ||
| CreateMap<Dentizone.Domain.Entity.Review, Dentizone.Application.DTOs.Review.ReviewDto>().ReverseMap(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| using FluentValidation; | ||
|
|
||
| namespace Dentizone.Application.DTOs.Review | ||
| { | ||
| public class CreateReviewDto | ||
| { | ||
| public string OrderId { get; set; } | ||
| public int Stars { get; set; } | ||
| public string Comment { get; set; } | ||
| } | ||
|
|
||
| public class CreateReviewDtoValidation : AbstractValidator<CreateReviewDto> | ||
| { | ||
| public CreateReviewDtoValidation() | ||
| { | ||
| RuleFor(x => x.OrderId) | ||
| .NotEmpty().WithMessage("Order ID is required.") | ||
| .NotNull().WithMessage("Order ID cannot be null."); | ||
|
|
||
| RuleFor(x => x.Stars) | ||
| .InclusiveBetween(1, 5).WithMessage("Stars must be between 1 and 5."); | ||
|
|
||
| RuleFor(x => x.Comment) | ||
| .MaximumLength(500).WithMessage("Comment must not exceed 500 characters."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using FluentValidation; | ||
|
|
||
| namespace Dentizone.Application.DTOs.Review | ||
| { | ||
| public class ReviewDto | ||
| { | ||
| public string Comment { get; set; } | ||
This comment was marked as resolved.
Sorry, something went wrong. |
||
| public int Stars { get; set; } | ||
| } | ||
|
|
||
| public class ReviewDtoValidation : AbstractValidator<ReviewDto> | ||
| { | ||
| public ReviewDtoValidation() | ||
| { | ||
| RuleFor(x => x.Comment) | ||
| .NotEmpty() | ||
| .NotNull().WithMessage("Comment cannot be null.") | ||
| .MaximumLength(500).WithMessage("Comment must not exceed 500 characters."); | ||
|
|
||
| RuleFor(x => x.Stars) | ||
| .InclusiveBetween(1, 5).WithMessage("Stars must be between 1 and 5."); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| using FluentValidation; | ||
|
|
||
| namespace Dentizone.Application.DTOs.Review | ||
| { | ||
| public class UpdateReviewDto | ||
| { | ||
| public string? Comment { get; set; } | ||
| public int Stars { get; set; } | ||
| } | ||
|
|
||
| public class UpdateReviewDtoValidation : AbstractValidator<UpdateReviewDto> | ||
| { | ||
| public UpdateReviewDtoValidation() | ||
| { | ||
| RuleFor(x => x.Comment) | ||
| .MaximumLength(500) | ||
| .When(x => x.Comment != null) | ||
| .WithMessage("Comment must not exceed 500 characters when provided."); | ||
| RuleFor(x => x.Stars) | ||
| .InclusiveBetween(1, 5).WithMessage("Stars must be between 1 and 5."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,5 +11,7 @@ public interface IOrderService | |||||
| Task<OrderViewDto?> CancelOrderAsync(string orderId, string userId); | ||||||
| Task CompleteOrder(string orderId); | ||||||
| Task<PagedResultDto<OrderViewDto>> GetOrders(int page, FilterOrderDto filters); | ||||||
|
|
||||||
| Task<IEnumerable<Domain.Entity.Order>> GetReviewedOrdersByUserId(string userId); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider returning DTOs for consistency. The new method returns domain entities ( -Task<IEnumerable<Domain.Entity.Order>> GetReviewedOrdersByUserId(string userId);
+Task<IEnumerable<OrderViewDto>> GetReviewedOrdersByUserId(string userId);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||
| using Dentizone.Application.DTOs.Review; | ||||||
|
|
||||||
| namespace Dentizone.Application.Interfaces.Review | ||||||
| { | ||||||
| public interface IReviewService | ||||||
| { | ||||||
| Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider returning the created review or review ID. The method currently returns - Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);
+ Task<ReviewDto> CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents💡 Verification agent 🧩 Analysis chainVerify data type consistency between DTOs and entity. There's a data type mismatch: 🏁 Script executed: #!/bin/bash
# Description: Check for data type consistency between DTOs and entity
echo "Checking Stars property types across DTOs and entity..."
echo "CreateReviewDto Stars type:"
rg -A 1 "Stars.*{.*set" --type cs | grep -A 1 "CreateReviewDto"
echo "Review entity Stars type:"
rg -A 1 "Stars.*{.*set" --type cs | grep -A 1 "Review.*:"
echo "UpdateReviewDto (checking if it has Stars):"
rg -A 1 "Stars.*{.*set" --type cs | grep -A 1 "UpdateReviewDto"Length of output: 843 🏁 Script executed: #!/bin/bash
set -e
echo "=== Locating CreateReviewDto ==="
dto_file=$(rg -l "class CreateReviewDto" --type cs)
echo "File: $dto_file"
echo "CreateReviewDto Stars property:"
rg -n "public .* Stars " "$dto_file" || echo "No Stars property found in CreateReviewDto"
echo
echo "=== Locating Review entity ==="
entity_file=$(rg -l "class Review" --type cs | grep -v "Dto")
echo "File: $entity_file"
echo "Review entity Stars property:"
rg -n "public .* Stars " "$entity_file" || echo "No Stars property found in Review entity"
echo
echo "=== Locating UpdateReviewDto ==="
update_file=$(rg -l "class UpdateReviewDto" --type cs)
echo "File: $update_file"
echo "UpdateReviewDto Stars property:"
rg -n "public .* Stars " "$update_file" || echo "No Stars property found in UpdateReviewDto"Length of output: 4207 🏁 Script executed: #!/bin/bash
set -e
echo "=== Locating Review entity ==="
entity_file=$(rg -l "class Review" --type cs | grep -i "Domain/Entity/Review\.cs$")
echo "Review entity file: $entity_file"
echo "Stars property in Review entity:"
rg -n "public .*Stars" "$entity_file" || echo "No Stars property found"
echo
echo "=== Locating ReviewDTO ==="
dto_file=$(rg -l "class ReviewDTO" --type cs)
echo "ReviewDTO file: $dto_file"
echo "Stars property in ReviewDTO:"
rg -n "public .*Stars" "$dto_file" || echo "No Stars property found"
echo
echo "=== Inspecting Automapper mapping ==="
profile_file=$(rg -l "class ReviewProfile" --type cs)
echo "ReviewProfile file: $profile_file"
echo "Mapping for Stars:"
rg -n "ForMember.*Stars" "$profile_file" || echo "No explicit mapping for Stars found"Length of output: 612 Ensure Stars type consistency between DTOs and the Review entity There’s a clear mismatch between the DTO and the domain model:
This discrepancy will break AutoMapper mappings or runtime validation. You should unify the types—either:
Example diff if switching the DTO to --- Dentizone.Application/DTOs/Review/CreateReviewDTO.cs
@@ Line 8
- public decimal Stars { get; set; }
+ public int Stars { get; set; }Also, consider adding a 🤖 Prompt for AI Agents |
||||||
| Task<bool> UpdateReviewAsync(string reviewId, UpdateReviewDto updateReviewDto); | ||||||
| Task<bool> DeleteReviewAsync(string reviewId); | ||||||
| Task<IEnumerable<ReviewDto>> GetSubmittedReviews(string userId); | ||||||
| Task<IEnumerable<ReviewDto>> GetReceivedReviews(string userId); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainClarify the business logic for GetReceivedReviews. The 🏁 Script executed: #!/bin/bash
# Description: Analyze the Review entity relationships and business context
echo "Checking Review entity relationships..."
echo "Review entity structure:"
rg -A 10 "class Review" --type cs
echo "Order entity User relationship:"
rg -A 5 -B 5 "User.*Order\|Order.*User" --type cs
echo "Checking for provider/recipient concepts in Order:"
rg -A 3 -B 3 "Provider\|Recipient\|Doctor\|Patient" --type csLength of output: 11002 🏁 Script executed: #!/bin/bash
# Display the IReviewService interface to confirm method declarations
echo "----- IReviewService Interface -----"
sed -n '1,200p' Dentizone.Application/Interfaces/Review/IReviewService.cs
# Search for any implementation of GetReceivedReviews across the codebase
echo "----- Searching for GetReceivedReviews Usage -----"
rg "GetReceivedReviews" -n --type csLength of output: 1367 🏁 Script executed: #!/bin/bash
# Show the body of GetReceivedReviews in ReviewService
rg -A15 -B5 "GetReceivedReviews" -n Dentizone.Application/Services/ReviewService.csLength of output: 719 🏁 Script executed: #!/bin/bash
# Locate the GetReviewedOrdersByUserId method in the Order service interface
echo "----- IOrderService Interface -----"
sed -n '1,200p' Dentizone.Application/Interfaces/Order/IOrderService.cs | grep -n "GetReviewedOrdersByUserId"
# Show the signature and surrounding context if found
rg -A5 -B5 "GetReviewedOrdersByUserId" -n --type cs Dentizone.Application/Interfaces/Order/IOrderService.cs
# Locate the implementation in the OrderService class
echo "----- OrderService Implementation -----"
rg -A15 -B5 "GetReviewedOrdersByUserId" -n Dentizone.Application/Services/OrderService.csLength of output: 1458 Clarify GetReceivedReviews semantics Please update the interface and/or implementation to make this explicit:
🤖 Prompt for AI Agents |
||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,7 +33,7 @@ internal class OrderService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async Task<OrderViewDto?> CancelOrderAsync(string orderId, string userId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var order = await orderRepository.FindBy(o => o.Id == orderId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [o => o.OrderStatuses, o => o.OrderItems]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [o => o.OrderStatuses, o => o.OrderItems]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (order == null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -79,7 +79,7 @@ internal class OrderService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await postService.UpdatePostStatus(post.Id, PostStatus.Active); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var seller = await authService.GetById(post.Seller.Id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await mailService.Send(seller.Email, "Order Cancelled", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Your post {post.Title} has been cancelled by the buyer. we relisted it now for sale again@!"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Your post {post.Title} has been cancelled by the buyer. we relisted it now for sale again@!"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -99,7 +99,7 @@ private async Task SendConfirmationEmail(List<string> sellerEmails, string buyer | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach (var email in sellerEmails) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await mailService.Send(email, "New Order Placed", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Your post has been sold. Wait for pickup. Order ID: {orderId}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $"Your post has been sold. Wait for pickup. Order ID: {orderId}"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -154,7 +154,7 @@ public async Task<string> CreateOrderAsync(CreateOrderDto createOrderDto, string | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await orderItemRepository.CreateAsync(orderItem); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a Sale Transaction for each order item | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await paymentService.CreateSaleTransaction( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payment.Id, post.Seller.Wallet.Id, post.Price); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| payment.Id, post.Seller.Wallet.Id, post.Price); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create Ship Info | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -262,11 +262,20 @@ public async Task<PagedResultDto<OrderViewDto>> GetOrders(int page, FilterOrderD | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var order = await orderRepository.GetAllAsync( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| page, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filterExpression | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| page, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filterExpression | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return mapper.Map<PagedResultDto<OrderViewDto>>(order); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async Task<IEnumerable<Order>> GetReviewedOrdersByUserId(string userId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var orders = await orderRepository.GetAllAsync( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| o => o.IsReviewed && o.OrderItems.Any(oi => oi.Post.SellerId == userId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return orders.Items; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+272
to
+279
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve method implementation and add validation. The method has several areas for improvement:
public async Task<IEnumerable<Order>> GetReviewedOrdersByUserId(string userId)
{
+ if (string.IsNullOrWhiteSpace(userId))
+ {
+ throw new ArgumentException("User ID cannot be null or empty.", nameof(userId));
+ }
+
+ try
+ {
var orders = await orderRepository.GetAllAsync(
null,
o => o.IsReviewed && o.OrderItems.Any(oi => oi.Post.SellerId == userId)
);
return orders.Items;
+ }
+ catch (Exception ex)
+ {
+ // Log the exception and rethrow or handle appropriately
+ throw new ApplicationException($"Failed to retrieve reviewed orders for user {userId}", ex);
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| using AutoMapper; | ||
| using Dentizone.Application.DTOs.Review; | ||
| using Dentizone.Application.Interfaces.Order; | ||
| using Dentizone.Application.Interfaces.Review; | ||
| using Dentizone.Domain.Entity; | ||
| using Dentizone.Domain.Interfaces.Repositories; | ||
| using Dentizone.Domain.Exceptions; | ||
| using Microsoft.EntityFrameworkCore; | ||
|
|
||
| namespace Dentizone.Application.Services | ||
| { | ||
| public class ReviewService(IMapper mapper, IReviewRepository repo, IOrderService orderService) : IReviewService | ||
| { | ||
| public async Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto) | ||
| { | ||
| var review = new Review() | ||
| { | ||
| OrderId = createReviewDto.OrderId, | ||
| Text = createReviewDto.Comment, | ||
| UserId = userId | ||
| }; | ||
|
|
||
| await repo.CreateAsync(review); | ||
| } | ||
|
|
||
| public async Task DeleteReviewAsync(string reviewId) | ||
| { | ||
| await repo.DeleteAsync(reviewId); | ||
| } | ||
|
Comment on lines
+26
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add parameter validation for consistency. public async Task DeleteReviewAsync(string reviewId)
{
+ if (string.IsNullOrWhiteSpace(reviewId))
+ throw new ArgumentException("Review ID cannot be null or empty", nameof(reviewId));
+
await repo.DeleteAsync(reviewId);
}🤖 Prompt for AI Agents |
||
|
|
||
| public async Task<IEnumerable<ReviewDto>> GetSubmittedReviews(string userId) | ||
| { | ||
| var review = repo.FindAllBy(r => r.UserId == userId && !r.IsDeleted); | ||
|
|
||
|
|
||
| return await review.Select(r => mapper.Map<ReviewDto>(r)).ToListAsync(); | ||
| } | ||
|
|
||
| public async Task UpdateReviewAsync(string reviewId, UpdateReviewDto updateReviewDto) | ||
| { | ||
| var review = await repo.GetByIdAsync(reviewId); | ||
| if (review == null || review.IsDeleted) | ||
| throw new NotFoundException("Review not found."); | ||
|
|
||
| review.Text = updateReviewDto.Comment; | ||
|
|
||
| await repo.Update(review); | ||
| } | ||
|
|
||
| public async Task<IEnumerable<ReviewDto>> GetReceivedReviews(string userId) | ||
| { | ||
| var reviews = await orderService.GetReviewedOrdersByUserId(userId); | ||
|
|
||
|
|
||
| // Get the reviews for the orders | ||
| var reviewDtos = reviews.Select(r => new ReviewDto | ||
| { | ||
| Comment = r.Review.Text ?? "No Comment", | ||
| Stars = r.Review.Stars, | ||
| }); | ||
|
|
||
| return reviewDtos.ToList(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing XSS Protection on Comment Field
Tell me more
What is the issue?
The Comment property lacks input sanitization rules in the validation to prevent potential XSS attacks.
Why this matters
Without proper sanitization, malicious users could inject harmful scripts that would be stored and potentially executed when the review is displayed to other users.
Suggested change ∙ Feature Preview
Add validation rule to sanitize the Comment input:
Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.