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
10 changes: 10 additions & 0 deletions Dentizone.Application/DTOs/Post/UpdatePostStateDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Dentizone.Domain.Enums;

namespace Dentizone.Application.DTOs.Post;

public class UpdatePostStateDto
{
public required string PostId { get; set; }
public required PostStatus Status { get; set; }
public string? Reason { get; set; }
}
2 changes: 1 addition & 1 deletion Dentizone.Application/Interfaces/IPostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public interface IPostService

Task<List<PostViewDto>> Search(UserPreferenceDto userPreferenceDto);
Task<List<Domain.Entity.Post>> ValidatePosts(List<string> postIds);
Task<PostViewDto> UpdatePostStatus(string postId, PostStatus status);
Task<PostViewDto> UpdatePostStatus(string postId, PostStatus status, string? reason);
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

Good addition for enhanced post status communication.

The optional reason parameter is a sensible enhancement that allows providing explanations when updating post status, particularly useful for rejections. The nullable string type is appropriate for this optional parameter.

Note that this is a breaking change to the interface - ensure all implementations of IPostService have been updated to include this parameter.


🏁 Script executed:

#!/bin/bash
# Description: Verify all implementations of IPostService have been updated with the new signature
# Expected: All implementations should include the reason parameter

# Search for classes implementing IPostService
ast-grep --pattern 'class $_ : $$$IPostService$$$'

# Search for UpdatePostStatus method implementations  
ast-grep --pattern 'UpdatePostStatus($$$) {'

Length of output: 99


I’ll double-check all C# files to find where IPostService is referenced, which classes implement it, and verify each UpdatePostStatus signature.


🏁 Script executed:

#!/bin/bash
echo "1) All references to IPostService:"
rg -n --no-heading --color never --glob '*.cs' "IPostService"

echo
echo "2) Classes implementing IPostService:"
rg -nP --no-heading --color never --glob '*.cs' "class\s+\w+\s*:\s*.*IPostService"

echo
echo "3) All UpdatePostStatus definitions/calls:"
rg -n --no-heading --color never --glob '*.cs' "UpdatePostStatus"

Length of output: 1981


Action Required: Update all UpdatePostStatus invocations to include the new reason argument

The nullable reason parameter is a sensible enhancement for communicating rejection details, but existing calls need to be updated to avoid breakage. Specifically:

  • Dentizone.Application/Services/OrderService.cs
    • Line 75: await postService.UpdatePostStatus(post.Id, PostStatus.Active);
    • Line 170: await postService.UpdatePostStatus(post.Id, PostStatus.Sold);
      → Add a third argument (e.g. null or a message) for the new reason parameter.

Verify there are no other two-argument calls to UpdatePostStatus across the codebase.

🤖 Prompt for AI Agents
In Dentizone.Application/Services/OrderService.cs at lines 75 and 170, the calls
to UpdatePostStatus currently pass only two arguments, but the method signature
now requires a third nullable reason parameter. Update these calls by adding a
third argument, such as null or an appropriate message string, to match the new
method signature. Also, search the entire codebase for any other calls to
UpdatePostStatus with only two arguments and update them similarly to prevent
compilation errors.

}
}
29 changes: 15 additions & 14 deletions Dentizone.Application/Services/OrderService.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
using AutoMapper;
using Dentizone.Application.DTOs;
using Dentizone.Application.DTOs.Order;
using Dentizone.Application.DTOs.Payment;
using Dentizone.Application.Interfaces;
using Dentizone.Domain.Entity;
using Dentizone.Domain.Enums;
using Dentizone.Domain.Exceptions;
using Dentizone.Domain.Interfaces.Mail;
using Dentizone.Domain.Interfaces.Repositories;
using Dentizone.Infrastructure;
using System.Linq.Expressions;
using Dentizone.Application.DTOs.Payment;
using Microsoft.AspNetCore.Http;
using System.Linq.Expressions;

namespace Dentizone.Application.Services
{
Expand All @@ -32,7 +32,7 @@ internal class OrderService(
public async Task<OrderViewDto?> CancelOrderAsync(string orderId)
{
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 @@ -72,10 +72,10 @@ internal class OrderService(
var post = await postService.GetPostById(orderItem.PostId);
if (post is not null)
{
await postService.UpdatePostStatus(post.Id, PostStatus.Active);
await postService.UpdatePostStatus(post.Id, PostStatus.Active, "Order Cancelled");
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 @@ -95,7 +95,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 @@ -150,7 +150,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 All @@ -167,7 +167,7 @@ await paymentService.CreateSaleTransaction(
// Mark the post as Sold
foreach (var post in posts)
{
await postService.UpdatePostStatus(post.Id, PostStatus.Sold);
await postService.UpdatePostStatus(post.Id, PostStatus.Sold, "Order Sold");
}

// Get Buyer and Seller Emails
Expand Down Expand Up @@ -273,19 +273,20 @@ public async Task<PagedResultDto<OrderViewAll>> GetOrders(int page, FilterOrderD


var order = await orderRepository.GetAllAsync(
page,
filterExpression
);
page,
filterExpression
);

return mapper.Map<PagedResultDto<OrderViewAll>>(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)
);
null,
o => o.IsReviewed &&
o.OrderItems.Any(oi => oi.Post.SellerId == userId)
);
return orders.Items;
}

Expand Down
41 changes: 38 additions & 3 deletions Dentizone.Application/Services/PostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Dentizone.Domain.Enums;
using Dentizone.Domain.Exceptions;
using Dentizone.Domain.Interfaces;
using Dentizone.Domain.Interfaces.Mail;
using Dentizone.Domain.Interfaces.Repositories;
using Dentizone.Infrastructure;
using Dentizone.Infrastructure.Cache;
Expand All @@ -24,7 +25,8 @@ public class PostService(
ISubCategoryRepository subCategoryRepository,
IAssetService assetService,
AppDbContext dbContext,
IRedisService redisService)
IRedisService redisService,
IMailService mailService)
: BaseService(accessor), IPostService
{
public async Task<List<Post>> ValidatePosts(List<string> postIds)
Expand Down Expand Up @@ -202,16 +204,28 @@ public async Task<PostViewDto> UpdatePost(string postId, UpdatePostDto updatePos
return mapper.Map<PostViewDto>(updatedPost);
}

public async Task<PostViewDto> UpdatePostStatus(string postId, PostStatus status)
public async Task<PostViewDto> UpdatePostStatus(string postId, PostStatus status, string? reason)
{
var post = await repo.GetByIdAsync(postId);
var post = await repo.GetAllAsync(p => p.Id == postId && !p.IsDeleted, includes: [p => p.Seller])
.FirstOrDefaultAsync();
if (post == null)
{
throw new NotFoundException("Post not found");
}


post.Status = status;
var updatedPost = await repo.UpdateAsync(post);

if (updatedPost == null)
{
throw new NotFoundException("Post not found");
}

// Notify the seller about the status change
await NotifySellerAsync(post, status, reason);


return mapper.Map<PostViewDto>(updatedPost);
}

Expand Down Expand Up @@ -337,5 +351,26 @@ protected override async Task<string> GetOwnerIdAsync(string resourceId)

return post.SellerId;
}

private async Task NotifySellerAsync(Post post, PostStatus status, string? reason)
{
var email = post.Seller.Email;
var postTitle = post.Title;

switch (status)
{
case PostStatus.Active:
await mailService.Send(email, "Post Approved", $"Your post '{postTitle}' has been approved");
break;

case PostStatus.Rejected when !string.IsNullOrEmpty(reason):
await mailService.Send(email, "Post Rejected",
$"Your post '{postTitle}' has been rejected. Reason: {reason}");
break;

default:
break;
}
}
}
}
16 changes: 12 additions & 4 deletions Dentizone.Presentaion/Controllers/PostsController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using Dentizone.Application.DTOs.Post;
using Dentizone.Application.DTOs.Post;
using Dentizone.Application.DTOs.Post.PostFilterDto;
using Dentizone.Application.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace Dentizone.Presentaion.Controllers
{
Expand Down Expand Up @@ -76,5 +76,13 @@ public async Task<IActionResult> Search([FromQuery] UserPreferenceDto userPrefer
var searchResult = await postService.Search(userPreferenceDto);
return Ok(searchResult);
}

[Authorize("IsAdmin")]
[HttpPatch("{postId}/status")]
public async Task<IActionResult> AdjustStatus([FromBody] UpdatePostStateDto state)
{
var updatedPost = await postService.UpdatePostStatus(state.PostId, state.Status, state.Reason);
return Ok(updatedPost);
}
}
}