Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions Dentizone.Application/AutoMapper/Payments/WalletProfile.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using AutoMapper;
using Dentizone.Application.Services.Payment;
using Dentizone.Domain.Entity;

Expand Down
14 changes: 14 additions & 0 deletions Dentizone.Application/AutoMapper/Review/ReviewProfile.cs
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();
}
}
}
4 changes: 4 additions & 0 deletions Dentizone.Application/DI/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Dentizone.Application.Interfaces.Favorites;
using Dentizone.Application.Interfaces.Order;
using Dentizone.Application.Interfaces.Post;
using Dentizone.Application.Interfaces.Review;
using Dentizone.Application.Interfaces.User;
using Dentizone.Application.Services;
using Dentizone.Application.Services.Authentication;
Expand Down Expand Up @@ -40,6 +41,9 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection

services.AddScoped<IWalletService, WalletService>();
services.AddScoped<IPaymentService, PaymentService>();

services.AddScoped<IReviewService, ReviewService>();

services.AddScoped<IWithdrawalService, WithdrawalService>();
return services;
}
Expand Down
27 changes: 27 additions & 0 deletions Dentizone.Application/DTOs/Review/CreateReviewDTO.cs
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; }
Copy link
Copy Markdown

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 category Security

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:

RuleFor(x => x.Comment)
    .MaximumLength(500)
    .Must(comment => HtmlSanitizer.Sanitize(comment) == comment)
    .WithMessage("Comment contains invalid characters or potentially harmful content.");
Provide feedback to improve future suggestions

Nice Catch Incorrect Not in Scope Not in coding standard Other

💬 Looking for more details? Reply to this comment to chat with Korbit.

}

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.");
}
}
}
24 changes: 24 additions & 0 deletions Dentizone.Application/DTOs/Review/ReviewDTO.cs
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.

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.");
}
}
}
23 changes: 23 additions & 0 deletions Dentizone.Application/DTOs/Review/UpdateReviewDTO.cs
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.");
}
}
}
2 changes: 2 additions & 0 deletions Dentizone.Application/Interfaces/Order/IOrderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 (Domain.Entity.Order) directly, which breaks the typical pattern used by other methods in this interface that return DTOs. For consistency and to maintain proper separation of concerns, consider returning IEnumerable<OrderViewDto> instead.

-Task<IEnumerable<Domain.Entity.Order>> GetReviewedOrdersByUserId(string userId);
+Task<IEnumerable<OrderViewDto>> GetReviewedOrdersByUserId(string userId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Task<IEnumerable<Domain.Entity.Order>> GetReviewedOrdersByUserId(string userId);
Task<IEnumerable<OrderViewDto>> GetReviewedOrdersByUserId(string userId);
🤖 Prompt for AI Agents
In Dentizone.Application/Interfaces/Order/IOrderService.cs at line 15, the
method GetReviewedOrdersByUserId currently returns domain entities
(Domain.Entity.Order), which is inconsistent with other methods that return
DTOs. Change the return type to IEnumerable<OrderViewDto> to maintain
consistency and proper separation of concerns in the interface.

}
}
13 changes: 13 additions & 0 deletions Dentizone.Application/Interfaces/Review/IReviewService.cs
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 which provides no information about the created review. Consider returning Task<ReviewDto> or Task<string> to provide the created review details or ID for further operations.

-        Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);
+        Task<ReviewDto> CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);
Task<ReviewDto> CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto);
🤖 Prompt for AI Agents
In Dentizone.Application/Interfaces/Review/IReviewService.cs at line 7, the
CreateOrderReviewAsync method returns Task without any result, which does not
provide information about the created review. Modify the method signature to
return Task<ReviewDto> or Task<string> to return the created review details or
its ID, enabling callers to access the created review information for further
processing.

💡 Verification agent

🧩 Analysis chain

Verify data type consistency between DTOs and entity.

There's a data type mismatch: CreateReviewDto.Stars is decimal while the Review entity's Stars property is int. This could cause mapping or validation issues.


🏁 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:

  • Dentizone.Application/DTOs/Review/CreateReviewDTO.cs
    Line 8:
    public decimal Stars { get; set; }
  • Dentizone.Domain/Entity/Review.cs
    Line 9:
    public int Stars { get; set; }

This discrepancy will break AutoMapper mappings or runtime validation. You should unify the types—either:

  • Change CreateReviewDTO.Stars (and any other DTOs) to int, or
  • Adjust the domain Review.Stars to decimal if fractional ratings are required.

Example diff if switching the DTO to int:

--- Dentizone.Application/DTOs/Review/CreateReviewDTO.cs
@@ Line 8
- public decimal Stars { get; set; }
+ public int Stars     { get; set; }

Also, consider adding a Stars property to UpdateReviewDTO (currently absent) to keep create/update models aligned.

🤖 Prompt for AI Agents
In Dentizone.Application/Interfaces/Review/IReviewService.cs at line 7, ensure
the Stars property type is consistent between the DTOs and the Review entity to
avoid mapping or validation errors. Either change the Stars property in
CreateReviewDTO (and other related DTOs) from decimal to int to match the Review
entity, or update the Review entity's Stars property to decimal if fractional
ratings are needed. Additionally, add a Stars property to UpdateReviewDTO to
keep the create and update models aligned.

Task<bool> UpdateReviewAsync(string reviewId, UpdateReviewDto updateReviewDto);
Task<bool> DeleteReviewAsync(string reviewId);
Task<IEnumerable<ReviewDto>> GetSubmittedReviews(string userId);
Task<IEnumerable<ReviewDto>> GetReceivedReviews(string userId);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Clarify the business logic for GetReceivedReviews.

The GetReceivedReviews method suggests users can "receive" reviews, but the Review entity appears to link reviews to orders and the users who submit them. Please clarify: does this method return reviews for orders where the user is the provider/recipient of the service being reviewed?


🏁 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 cs

Length 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 cs

Length of output: 1367


🏁 Script executed:

#!/bin/bash
# Show the body of GetReceivedReviews in ReviewService
rg -A15 -B5 "GetReceivedReviews" -n Dentizone.Application/Services/ReviewService.cs

Length 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.cs

Length of output: 1458


Clarify GetReceivedReviews semantics
GetReceivedReviews actually calls orderService.GetReviewedOrdersByUserId, which returns orders where OrderItems.Any(oi => oi.Post.SellerId == userId) and IsReviewed == true. In other words, this method returns the reviews that a user has received as the seller/provider of a post.

Please update the interface and/or implementation to make this explicit:

  • Add or update the XML doc on IReviewService.GetReceivedReviews to state:
    “Retrieves reviews for orders where the specified user is the seller (provider).”
  • (Optional) Consider renaming the method to something like GetReviewsReceivedAsSellerAsync or GetProviderReviewsAsync for clarity.
🤖 Prompt for AI Agents
In Dentizone.Application/Interfaces/Review/IReviewService.cs at line 11, the
method GetReceivedReviews lacks clarity about its semantics. Update the XML
documentation comment for this method to explicitly state that it retrieves
reviews for orders where the specified user is the seller or provider.
Optionally, rename the method to a more descriptive name such as
GetReviewsReceivedAsSellerAsync or GetProviderReviewsAsync to better reflect its
purpose.

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Dentizone.Application.DTOs.Auth;
using Dentizone.Application.DTOs.User;
using Dentizone.Application.Interfaces;
using Dentizone.Application.Services.Payment;
using Dentizone.Domain.Enums;
using Dentizone.Domain.Exceptions;
using Dentizone.Domain.Interfaces;
Expand Down
23 changes: 16 additions & 7 deletions Dentizone.Application/Services/OrderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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@!");
}
}

Expand All @@ -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}");
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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:

  1. Parameter validation: No null/empty check for userId
  2. Potential N+1 queries: The filter expression could cause performance issues with nested navigation properties
  3. Missing error handling: No exception handling for database operations
 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
}
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);
}
}
🤖 Prompt for AI Agents
In Dentizone.Application/Services/OrderService.cs around lines 272 to 279,
improve the GetReviewedOrdersByUserId method by first validating that the userId
parameter is not null or empty and throwing an appropriate exception if it is.
Refactor the query to avoid potential N+1 issues by including necessary related
entities eagerly or restructuring the query to optimize database access. Add
try-catch blocks around the database call to handle exceptions gracefully and
log or rethrow them as needed.

}
}
39 changes: 12 additions & 27 deletions Dentizone.Application/Services/Payment/WalletService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,40 +69,25 @@ public async Task CreateWallet(string userId)

public async Task<WalletView> AddToBalance(decimal amount, string walletId)
{
if (amount == 0)
var wallet = await walletRepository.FindBy(w => w.Id == walletId && w.Status == UserWallet.ACTIVE);

if (wallet is null)
{
throw new ArgumentException("Amount must not be zero.", nameof(amount));
throw new BadActionException("No Wallet for this Wallet Id");
}

await using var transaction = await dbContext.Database.BeginTransactionAsync();
try
var newBalance = wallet.Balance + amount;
if (newBalance < 0)
{
var wallet = await walletRepository.FindBy(w => w.Id == walletId && w.Status == UserWallet.ACTIVE);

if (wallet is null)
{
throw new BadActionException("No Wallet for this Wallet Id");
}

var newBalance = wallet.Balance + amount;
if (newBalance < 0)
{
throw new BadActionException("Insufficient wallet balance.");
}
throw new BadActionException("Insufficient wallet balance.");
}

wallet.Balance = newBalance;
await walletRepository.UpdateAsync(wallet);
wallet.Balance = newBalance;
await walletRepository.UpdateAsync(wallet);

await transaction.CommitAsync();

var view = mapper.Map<WalletView>(wallet);
return view;
}
catch
{
await transaction.RollbackAsync();
throw;
}
var view = mapper.Map<WalletView>(wallet);
return view;
}

public async Task<WalletView> GetWalletBalanceAsync(string userId)
Expand Down
65 changes: 65 additions & 0 deletions Dentizone.Application/Services/ReviewService.cs
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
In Dentizone.Application/Services/ReviewService.cs around lines 30 to 33, the
DeleteReviewAsync method lacks parameter validation for the reviewId argument.
Add a check at the start of the method to validate that reviewId is not null or
empty, and throw an appropriate exception (e.g., ArgumentNullException or
ArgumentException) if the validation fails to maintain consistency with other
methods.


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();
}
}
}
19 changes: 11 additions & 8 deletions Dentizone.Application/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
using Dentizone.Domain.Exceptions;
using Dentizone.Domain.Interfaces.Repositories;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;

namespace Dentizone.Application.Services
{
public class UserService(IUserRepository userRepository, IMapper mapper, IWalletService walletService, Infrastructure.AppDbContext dbContext)
public class UserService(
IUserRepository userRepository,
IMapper mapper,
IWalletService walletService,
Infrastructure.AppDbContext dbContext)
: IUserService
{
public async Task<UserView> CreateAsync(CreateAppUser userDto)
Expand Down Expand Up @@ -46,7 +49,7 @@ public async Task<UserView> DeleteAsync(string id)
}

public async Task<ICollection<UserView>> GetAllAsync(int page, string? searchByName = null,
Expression<Func<AppUser, bool>>? filterExpression = null)
Expression<Func<AppUser, bool>>? filterExpression = null)
{
var users = await userRepository.GetAllAsync(page, filterExpression);
if (users == null)
Expand All @@ -57,7 +60,7 @@ public async Task<ICollection<UserView>> GetAllAsync(int page, string? searchByN
if (!string.IsNullOrEmpty(searchByName))
{
users = users.Where(u => u.FullName.Contains(searchByName, StringComparison.OrdinalIgnoreCase))
.ToList();
.ToList();
}

return mapper.Map<ICollection<UserView>>(users);
Expand All @@ -66,10 +69,10 @@ public async Task<ICollection<UserView>> GetAllAsync(int page, string? searchByN
public async Task<DomainUserView> GetByIdAsync(string id)
{
var user = await userRepository.FindBy(u => u.Id == id,
[
u => u.University
]
);
[
u => u.University
]
);
if (user == null)
{
throw new NotFoundException($"User with id {id} not found.");
Expand Down
1 change: 0 additions & 1 deletion Dentizone.Domain/Entity/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ public class Order : IBaseEntity, IDeletable, IUpdatable
public DateTime UpdatedAt { get; set; }
public bool IsDeleted { get; set; }
public virtual Review Review { get; set; }
public virtual ReviewUx ReviewUx { get; set; }
}
}
Loading
Loading