diff --git a/Dentizone.Application/AutoMapper/Payments/WalletProfile.cs b/Dentizone.Application/AutoMapper/Payments/WalletProfile.cs index 2f99d9a..2972cf4 100644 --- a/Dentizone.Application/AutoMapper/Payments/WalletProfile.cs +++ b/Dentizone.Application/AutoMapper/Payments/WalletProfile.cs @@ -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; diff --git a/Dentizone.Application/AutoMapper/Review/ReviewProfile.cs b/Dentizone.Application/AutoMapper/Review/ReviewProfile.cs new file mode 100644 index 0000000..5c087a5 --- /dev/null +++ b/Dentizone.Application/AutoMapper/Review/ReviewProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; + +namespace Dentizone.Application.AutoMapper.Review +{ + internal class ReviewProfile : Profile + { + public ReviewProfile() + { + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DI/Services.cs b/Dentizone.Application/DI/Services.cs index fb35a14..61debf4 100644 --- a/Dentizone.Application/DI/Services.cs +++ b/Dentizone.Application/DI/Services.cs @@ -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; @@ -40,6 +41,9 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection services.AddScoped(); services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/Dentizone.Application/DTOs/Review/CreateReviewDTO.cs b/Dentizone.Application/DTOs/Review/CreateReviewDTO.cs new file mode 100644 index 0000000..93e3234 --- /dev/null +++ b/Dentizone.Application/DTOs/Review/CreateReviewDTO.cs @@ -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 + { + 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."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Review/ReviewDTO.cs b/Dentizone.Application/DTOs/Review/ReviewDTO.cs new file mode 100644 index 0000000..f8030ff --- /dev/null +++ b/Dentizone.Application/DTOs/Review/ReviewDTO.cs @@ -0,0 +1,24 @@ +using FluentValidation; + +namespace Dentizone.Application.DTOs.Review +{ + public class ReviewDto + { + public string Comment { get; set; } + public int Stars { get; set; } + } + + public class ReviewDtoValidation : AbstractValidator + { + 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."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/DTOs/Review/UpdateReviewDTO.cs b/Dentizone.Application/DTOs/Review/UpdateReviewDTO.cs new file mode 100644 index 0000000..190b613 --- /dev/null +++ b/Dentizone.Application/DTOs/Review/UpdateReviewDTO.cs @@ -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 + { + 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."); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/Interfaces/Order/IOrderService.cs b/Dentizone.Application/Interfaces/Order/IOrderService.cs index 586d541..76efa66 100644 --- a/Dentizone.Application/Interfaces/Order/IOrderService.cs +++ b/Dentizone.Application/Interfaces/Order/IOrderService.cs @@ -11,5 +11,7 @@ public interface IOrderService Task CancelOrderAsync(string orderId, string userId); Task CompleteOrder(string orderId); Task> GetOrders(int page, FilterOrderDto filters); + + Task> GetReviewedOrdersByUserId(string userId); } } \ No newline at end of file diff --git a/Dentizone.Application/Interfaces/Review/IReviewService.cs b/Dentizone.Application/Interfaces/Review/IReviewService.cs new file mode 100644 index 0000000..429bf3c --- /dev/null +++ b/Dentizone.Application/Interfaces/Review/IReviewService.cs @@ -0,0 +1,13 @@ +using Dentizone.Application.DTOs.Review; + +namespace Dentizone.Application.Interfaces.Review +{ + public interface IReviewService + { + Task CreateOrderReviewAsync(string userId, CreateReviewDto createReviewDto); + Task UpdateReviewAsync(string reviewId, UpdateReviewDto updateReviewDto); + Task DeleteReviewAsync(string reviewId); + Task> GetSubmittedReviews(string userId); + Task> GetReceivedReviews(string userId); + } +} \ No newline at end of file diff --git a/Dentizone.Application/Services/Authentication/AuthService.cs b/Dentizone.Application/Services/Authentication/AuthService.cs index 84050cb..cae13a5 100644 --- a/Dentizone.Application/Services/Authentication/AuthService.cs +++ b/Dentizone.Application/Services/Authentication/AuthService.cs @@ -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; diff --git a/Dentizone.Application/Services/OrderService.cs b/Dentizone.Application/Services/OrderService.cs index 7de66c4..94f266f 100644 --- a/Dentizone.Application/Services/OrderService.cs +++ b/Dentizone.Application/Services/OrderService.cs @@ -33,7 +33,7 @@ internal class OrderService( public async Task 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 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 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> GetOrders(int page, FilterOrderD var order = await orderRepository.GetAllAsync( - page, - filterExpression - ); + page, + filterExpression + ); return mapper.Map>(order); } + + public async Task> GetReviewedOrdersByUserId(string userId) + { + var orders = await orderRepository.GetAllAsync( + null, + o => o.IsReviewed && o.OrderItems.Any(oi => oi.Post.SellerId == userId) + ); + return orders.Items; + } } } \ No newline at end of file diff --git a/Dentizone.Application/Services/Payment/WalletService.cs b/Dentizone.Application/Services/Payment/WalletService.cs index 9cea597..cf995be 100644 --- a/Dentizone.Application/Services/Payment/WalletService.cs +++ b/Dentizone.Application/Services/Payment/WalletService.cs @@ -69,40 +69,25 @@ public async Task CreateWallet(string userId) public async Task 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(wallet); - return view; - } - catch - { - await transaction.RollbackAsync(); - throw; - } + var view = mapper.Map(wallet); + return view; } public async Task GetWalletBalanceAsync(string userId) diff --git a/Dentizone.Application/Services/ReviewService.cs b/Dentizone.Application/Services/ReviewService.cs new file mode 100644 index 0000000..f230458 --- /dev/null +++ b/Dentizone.Application/Services/ReviewService.cs @@ -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); + } + + public async Task> GetSubmittedReviews(string userId) + { + var review = repo.FindAllBy(r => r.UserId == userId && !r.IsDeleted); + + + return await review.Select(r => mapper.Map(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> 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(); + } + } +} \ No newline at end of file diff --git a/Dentizone.Application/Services/UserService.cs b/Dentizone.Application/Services/UserService.cs index e02dbb0..75f31cc 100644 --- a/Dentizone.Application/Services/UserService.cs +++ b/Dentizone.Application/Services/UserService.cs @@ -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 CreateAsync(CreateAppUser userDto) @@ -46,7 +49,7 @@ public async Task DeleteAsync(string id) } public async Task> GetAllAsync(int page, string? searchByName = null, - Expression>? filterExpression = null) + Expression>? filterExpression = null) { var users = await userRepository.GetAllAsync(page, filterExpression); if (users == null) @@ -57,7 +60,7 @@ public async Task> GetAllAsync(int page, string? searchByN if (!string.IsNullOrEmpty(searchByName)) { users = users.Where(u => u.FullName.Contains(searchByName, StringComparison.OrdinalIgnoreCase)) - .ToList(); + .ToList(); } return mapper.Map>(users); @@ -66,10 +69,10 @@ public async Task> GetAllAsync(int page, string? searchByN public async Task 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."); diff --git a/Dentizone.Domain/Entity/Order.cs b/Dentizone.Domain/Entity/Order.cs index 7806f46..3769bf4 100644 --- a/Dentizone.Domain/Entity/Order.cs +++ b/Dentizone.Domain/Entity/Order.cs @@ -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; } } } \ No newline at end of file diff --git a/Dentizone.Domain/Entity/ReviewUX.cs b/Dentizone.Domain/Entity/ReviewUX.cs deleted file mode 100644 index 19625e0..0000000 --- a/Dentizone.Domain/Entity/ReviewUX.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Dentizone.Domain.Enums; -using Dentizone.Domain.Interfaces; - -namespace Dentizone.Domain.Entity -{ - public class ReviewUx : IBaseEntity , IDeletable, IUpdatable - { - public string Id { get; set; } = Guid.NewGuid().ToString(); - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public bool IsDeleted { get; set; } - - - public string UserId { get; set; } - public virtual AppUser User { get; set; } - - public virtual Order Order { get; set; } - public string OrderId { get; set; } - - public string? Text { get; set; } - public int Stars { get; set; } - - public ReviewStatus Status { get; set; } - } -} \ No newline at end of file diff --git a/Dentizone.Domain/Interfaces/Repositories/IOrderRepository.cs b/Dentizone.Domain/Interfaces/Repositories/IOrderRepository.cs index c32cd4a..293ed6d 100644 --- a/Dentizone.Domain/Interfaces/Repositories/IOrderRepository.cs +++ b/Dentizone.Domain/Interfaces/Repositories/IOrderRepository.cs @@ -8,7 +8,7 @@ public interface IOrderRepository : IBaseRepo Task UpdateAsync(Order entity); Task GetOrderDetails(string orderId, string buyerId); Task> GetOrdersWithDetails(string buyerId); - Task> GetAllAsync(int page, Expression> filter); + Task> GetAllAsync(int? page, Expression> filter); Task CountTotalOrders(); Task AverageValueOfOrders(); diff --git a/Dentizone.Domain/Interfaces/Repositories/IReviewRepository.cs b/Dentizone.Domain/Interfaces/Repositories/IReviewRepository.cs index 281346a..74eed21 100644 --- a/Dentizone.Domain/Interfaces/Repositories/IReviewRepository.cs +++ b/Dentizone.Domain/Interfaces/Repositories/IReviewRepository.cs @@ -1,4 +1,5 @@ -using Dentizone.Domain.Entity; +using System.Linq.Expressions; +using Dentizone.Domain.Entity; namespace Dentizone.Domain.Interfaces.Repositories; @@ -6,4 +7,5 @@ public interface IReviewRepository : IBaseRepo { Task Update(Review entity); Task DeleteAsync(string id); + IQueryable FindAllBy(Expression> condition); } \ No newline at end of file diff --git a/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs b/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs index c1fbf60..05c564a 100644 --- a/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs +++ b/Dentizone.Infrastructure/DependencyInjection/AddRepositories.cs @@ -25,7 +25,15 @@ public static IServiceCollection AddRepositories(this IServiceCollection service services.AddScoped(); services.AddScoped(); services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + + services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/Dentizone.Infrastructure/Persistence/Configurations/ReviewUxConfiguration.cs b/Dentizone.Infrastructure/Persistence/Configurations/ReviewUxConfiguration.cs deleted file mode 100644 index 1a0b36a..0000000 --- a/Dentizone.Infrastructure/Persistence/Configurations/ReviewUxConfiguration.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Dentizone.Domain.Entity; -using Dentizone.Domain.Enums; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace Dentizone.Infrastructure.Persistence.Configurations -{ - internal class ReviewUxConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(r => r.Id); - - builder.Property(r => r.Id) - .IsRequired(); - builder.Property(r => r.UserId) - .IsRequired(); - builder.Property(r => r.IsDeleted) - .IsRequired() - .HasDefaultValue(false); - - - builder.Property(r => r.Text) - .HasMaxLength(100); - - builder.Property(r => r.Stars) - .HasDefaultValue(5); - - - builder.Property(r => r.OrderId) - .IsRequired(); - - builder.Property(r => r.Status) - .HasDefaultValue(ReviewStatus.PENDING) - .HasConversion(); - - - builder.Property(r => r.CreatedAt) - .HasDefaultValueSql(SqlCommon.Date) - .ValueGeneratedOnAdd(); - - builder.Property(r => r.UpdatedAt) - .IsRequired(); - - builder.HasOne(r => r.User) - .WithMany(u => u.UXReviews) - .HasForeignKey(r => r.UserId) - .OnDelete(DeleteBehavior.NoAction); - - - builder.HasOne(r => r.Order) - .WithOne(r => r.ReviewUx) - .HasForeignKey(r => r.OrderId) - .OnDelete(DeleteBehavior.NoAction); - } - } -} \ No newline at end of file diff --git a/Dentizone.Infrastructure/Repositories/OrderRepository.cs b/Dentizone.Infrastructure/Repositories/OrderRepository.cs index 7939e22..b7cdfea 100644 --- a/Dentizone.Infrastructure/Repositories/OrderRepository.cs +++ b/Dentizone.Infrastructure/Repositories/OrderRepository.cs @@ -11,7 +11,7 @@ public class OrderRepository(AppDbContext dbContext) : AbstractRepository(dbCont public async Task GetByIdAsync(string id) { return await dbContext.Orders - .FirstOrDefaultAsync(o => o.Id == id); + .FirstOrDefaultAsync(o => o.Id == id); } public async Task UpdateAsync(Order entity) @@ -29,7 +29,7 @@ public async Task CreateAsync(Order entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes = null) + Expression>[]? includes = null) { IQueryable query = dbContext.Orders; if (includes != null) @@ -43,30 +43,46 @@ public async Task CreateAsync(Order entity) return await query.FirstOrDefaultAsync(condition); } - private IQueryable BuildPagedQuery(int page, Expression>? filter) + private IQueryable BuildPagedQuery(int page, Expression>? filter, + IQueryable query) { - IQueryable query = dbContext.Orders; + if (page < 1) + { + page = 1; + } + + // Always order before pagination + query = query.OrderByDescending(o => o.CreatedAt); + + // Apply filter if present if (filter != null) { - query = query.OrderByDescending(o => o.CreatedAt) - .Skip(CalculatePagination(page)) - .Take(DefaultPageSize); + query = query.Where(filter); } + // Always apply pagination + query = query.Skip(CalculatePagination(page)).Take(DefaultPageSize); + return query; } - public async Task> GetAllAsync(int page, Expression> filter) + public async Task> GetAllAsync(int? page, Expression> filter) { - var query = BuildPagedQuery(page, filter); + var query = dbContext.Orders.AsQueryable(); + if (page is not null) + { + query = BuildPagedQuery(page.Value, filter, query); + } + var totalCount = await query.CountAsync(); query = query.Include(o => o.Buyer) - .Include(o => o.OrderItems) - .ThenInclude(p => p.Post) - .Include(o => o.ShipInfo) - .Include(o => o.OrderStatuses); + .Include(o => o.OrderItems) + .ThenInclude(p => p.Post) + .Include(o => o.ShipInfo) + .Include(o => o.OrderStatuses) + .Include(o => o.Review); query = query.Where(filter); @@ -74,7 +90,7 @@ public async Task> GetAllAsync(int page, Expression { Items = await query.AsNoTracking().ToListAsync(), - Page = page, + Page = page ?? 1, PageSize = DefaultPageSize, TotalCount = totalCount }; @@ -84,28 +100,28 @@ public async Task> GetAllAsync(int page, Expression GetOrderDetails(string orderId, string buyerId) { var query = dbContext.Orders - .AsNoTracking() - .Where(o => o.Id == orderId && o.BuyerId == buyerId) - .Include(o => o.Buyer) - .Include(o => o.OrderItems) - .ThenInclude(p => p.Post) - .Include(o => o.ShipInfo) - .Include(o => o.OrderStatuses); + .AsNoTracking() + .Where(o => o.Id == orderId && o.BuyerId == buyerId) + .Include(o => o.Buyer) + .Include(o => o.OrderItems) + .ThenInclude(p => p.Post) + .Include(o => o.ShipInfo) + .Include(o => o.OrderStatuses); return await query.FirstOrDefaultAsync(); } public async Task> GetOrdersWithDetails(string buyerId) { return await dbContext.Orders - .AsNoTracking() - .Where(o => o.BuyerId == buyerId) - .Include(o => o.Buyer) - .Include(o => o.OrderItems) - .ThenInclude(p => p.Post) - .Include(o => o.ShipInfo) - .Include(o => o.OrderStatuses) - .OrderByDescending(o => o.CreatedAt) - .ToListAsync(); + .AsNoTracking() + .Where(o => o.BuyerId == buyerId) + .Include(o => o.Buyer) + .Include(o => o.OrderItems) + .ThenInclude(p => p.Post) + .Include(o => o.ShipInfo) + .Include(o => o.OrderStatuses) + .OrderByDescending(o => o.CreatedAt) + .ToListAsync(); } public async Task CountTotalOrders() @@ -117,9 +133,9 @@ public async Task CountTotalOrders() public async Task AverageValueOfOrders() { var average = await dbContext.Orders - .AsNoTracking() - .Where(o => !o.IsDeleted) - .AverageAsync(o => (decimal?)o.TotalAmount); + .AsNoTracking() + .Where(o => !o.IsDeleted) + .AverageAsync(o => (decimal?)o.TotalAmount); return average ?? 0m; } } diff --git a/Dentizone.Infrastructure/Repositories/ReviewRepository.cs b/Dentizone.Infrastructure/Repositories/ReviewRepository.cs index aa8cf76..831558e 100644 --- a/Dentizone.Infrastructure/Repositories/ReviewRepository.cs +++ b/Dentizone.Infrastructure/Repositories/ReviewRepository.cs @@ -10,7 +10,7 @@ internal class ReviewRepository(AppDbContext dbContext) : AbstractRepository(dbC public async Task GetByIdAsync(string id) { return await dbContext.Reviews - .FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted); + .FirstOrDefaultAsync(r => r.Id == id && !r.IsDeleted); } @@ -22,7 +22,7 @@ public async Task CreateAsync(Review entity) } public async Task FindBy(Expression> condition, - Expression>[]? includes) + Expression>[]? includes) { IQueryable query = dbContext.Reviews; if (includes != null) @@ -54,5 +54,11 @@ public async Task Update(Review entity) await dbContext.SaveChangesAsync(); return entity; } + + public IQueryable FindAllBy(Expression> condition) + { + return dbContext.Reviews + .Where(condition).AsQueryable(); + } } } \ No newline at end of file diff --git a/Dentizone.Presentaion/Controllers/ReviewController.cs b/Dentizone.Presentaion/Controllers/ReviewController.cs new file mode 100644 index 0000000..6ab1495 --- /dev/null +++ b/Dentizone.Presentaion/Controllers/ReviewController.cs @@ -0,0 +1,53 @@ +using System.Security.Claims; +using Dentizone.Application.DTOs.Review; +using Dentizone.Application.Interfaces.Review; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dentizone.Presentaion.Controllers +{ + [ApiController] + [Route("api/[controller]")] + [Authorize] + public class ReviewController(IReviewService reviewService) : ControllerBase + { + [HttpPost] + public async Task CreateOrderReview([FromBody] CreateReviewDto createReviewDto) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + + await reviewService.CreateOrderReviewAsync(userId, createReviewDto); + return Ok(); + } + + [HttpPut("{reviewId}")] // ADMIN ONLY + public async Task UpdateReview(string reviewId, [FromBody] UpdateReviewDto updateReviewDto) + { + await reviewService.UpdateReviewAsync(reviewId, updateReviewDto); + return Ok(); + } + + [HttpDelete("{reviewId}")] // ADMIN ONLY + public async Task DeleteReview(string reviewId) + { + await reviewService.DeleteReviewAsync(reviewId); + return Ok(); + } + + [HttpGet] + public async Task GetSubmittedReviews() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + + var reviews = await reviewService.GetSubmittedReviews(userId); + return Ok(reviews); + } + + [HttpGet("received-review")] + public async Task GetReceivedReviews() + { + var reviews = await reviewService.GetReceivedReviews(User.FindFirstValue(ClaimTypes.NameIdentifier)); + return Ok(reviews); + } + } +} \ No newline at end of file diff --git a/Dentizone.Presentaion/Program.cs b/Dentizone.Presentaion/Program.cs index d4fedf4..9ce67ab 100644 --- a/Dentizone.Presentaion/Program.cs +++ b/Dentizone.Presentaion/Program.cs @@ -4,7 +4,6 @@ using Dentizone.Infrastructure.DependencyInjection; using Dentizone.Infrastructure.Filters; using Dentizone.Infrastructure.Identity; -using Dentizone.Infrastructure.Persistence.Seeder; using Dentizone.Presentaion.Context; using Dentizone.Presentaion.Extensions; using Dentizone.Presentaion.Middlewares;