feat: Implement user permission checks and manual payment functionalities

- Added CheckUserPermissionQuery and CheckUserPermissionQueryHandler for permission validation.
- Introduced GetUserRolesQuery and GetUserRolesQueryHandler to retrieve user roles.
- Created IPermissionService interface and its implementation in PermissionService.
- Defined permission and role constants in PermissionDefinitions.
- Developed SetDefaultVatPercentageCommand and its handler for VAT configuration.
- Implemented GetCurrentVatPercentageQuery and handler to fetch current VAT settings.
- Added manual payment commands: CreateManualPayment, ApproveManualPayment, and RejectManualPayment with respective handlers and validators.
- Created GetManualPaymentsQuery and handler for retrieving manual payment records.
- Integrated gRPC services for manual payments with appropriate permission checks.
- Established Protobuf definitions for manual payment operations and metadata.
This commit is contained in:
masoodafar-web
2025-12-05 17:27:38 +03:30
parent 67b43fea7a
commit 4aa9f28f6e
51 changed files with 1294 additions and 107 deletions

View File

@@ -31,5 +31,6 @@
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Role.Protobuf\BackOffice.BFF.Role.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.UserRole.Protobuf\BackOffice.BFF.UserRole.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.Category.Protobuf\BackOffice.BFF.Category.Protobuf.csproj" />
<ProjectReference Include="..\Protobufs\BackOffice.BFF.ManualPayment.Protobuf\BackOffice.BFF.ManualPayment.Protobuf.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,79 @@
using System;
using System.Threading.Tasks;
using BackOffice.BFF.Application.Common.Interfaces;
using Grpc.AspNetCore.Server;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.WebApi.Common.Authorization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class RequiresPermissionAttribute : Attribute
{
public RequiresPermissionAttribute(string permission)
{
Permission = permission ?? throw new ArgumentNullException(nameof(permission));
}
public string Permission { get; }
}
public class PermissionInterceptor : Interceptor
{
private readonly IPermissionService _permissionService;
private readonly ILogger<PermissionInterceptor> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public PermissionInterceptor(
IPermissionService permissionService,
ILogger<PermissionInterceptor> logger,
IHttpContextAccessor httpContextAccessor)
{
_permissionService = permissionService;
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
await EnsureHasPermissionAsync(context);
return await continuation(request, context);
}
private async Task EnsureHasPermissionAsync(ServerCallContext context)
{
var httpContext = context.GetHttpContext() ?? _httpContextAccessor.HttpContext;
if (httpContext == null)
{
return;
}
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
return;
}
var permissionAttributes = endpoint.Metadata.GetOrderedMetadata<RequiresPermissionAttribute>();
if (permissionAttributes == null || permissionAttributes.Count == 0)
{
return;
}
foreach (var attribute in permissionAttributes)
{
var hasPermission = await _permissionService.HasPermissionAsync(attribute.Permission, httpContext.RequestAborted);
if (!hasPermission)
{
_logger.LogWarning("Permission denied for permission {Permission}", attribute.Permission);
throw new RpcException(new Status(StatusCode.PermissionDenied, "Permission denied"));
}
}
}
}

View File

@@ -9,3 +9,5 @@ global using Microsoft.AspNetCore.Builder;
global using System;
global using Microsoft.AspNetCore.Routing;
global using System.Linq;
global using BackOffice.BFF.WebApi.Common.Authorization;
global using BackOffice.BFF.Application.Common.Models;

View File

@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core;
using Serilog;
using Serilog.Core;
using Microsoft.OpenApi.Models;
using BackOffice.BFF.WebApi.Common.Authorization;
var builder = WebApplication.CreateBuilder(args);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
@@ -37,6 +38,7 @@ builder.Services.AddGrpc(options =>
options.EnableDetailedErrors = true;
options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB
options.MaxSendMessageSize = 1000 * 1024 * 1024; // 1 GB
options.Interceptors.Add<PermissionInterceptor>();
}).AddJsonTranscoding();
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddApplicationServices();

View File

@@ -21,6 +21,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
public override async Task<Empty> CreateOrUpdateConfiguration(
CreateOrUpdateConfigurationRequest request,
ServerCallContext context)
@@ -30,6 +31,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
context);
}
[RequiresPermission(PermissionNames.SettingsManageConfiguration)]
public override async Task<Empty> DeactivateConfiguration(
DeactivateConfigurationRequest request,
ServerCallContext context)
@@ -39,6 +41,7 @@ public class ConfigurationService : ConfigurationContract.ConfigurationContractB
context);
}
[RequiresPermission(PermissionNames.SettingsView)]
public override async Task<GetAllConfigurationsResponse> GetAllConfigurations(
GetAllConfigurationsRequest request,
ServerCallContext context)

View File

@@ -0,0 +1,76 @@
using BackOffice.BFF.ManualPayment.Protobuf;
using BackOffice.BFF.WebApi.Common.Services;
using BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
using BackOffice.BFF.Application.ManualPaymentCQ.Commands.ApproveManualPayment;
using BackOffice.BFF.Application.ManualPaymentCQ.Commands.RejectManualPayment;
using BackOffice.BFF.Application.ManualPaymentCQ.Queries.GetManualPayments;
using Google.Protobuf.WellKnownTypes;
using Mapster;
using MediatR;
namespace BackOffice.BFF.WebApi.Services;
public class ManualPaymentService : ManualPaymentContract.ManualPaymentContractBase
{
private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS;
private readonly ISender _sender;
public ManualPaymentService(
IDispatchRequestToCQRS dispatchRequestToCQRS,
ISender sender)
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
_sender = sender;
}
[RequiresPermission(PermissionNames.ManualPaymentsCreate)]
public override async Task<CreateManualPaymentResponse> CreateManualPayment(
CreateManualPaymentRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateManualPaymentRequest, CreateManualPaymentCommand, CreateManualPaymentResponse>(
request,
context);
}
[RequiresPermission(PermissionNames.ManualPaymentsApprove)]
public override async Task<Google.Protobuf.WellKnownTypes.Empty> ApproveManualPayment(
ApproveManualPaymentRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ApproveManualPaymentRequest, ApproveManualPaymentCommand>(
request,
context);
}
[RequiresPermission(PermissionNames.ManualPaymentsApprove)]
public override async Task<Google.Protobuf.WellKnownTypes.Empty> RejectManualPayment(
RejectManualPaymentRequest request,
ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<RejectManualPaymentRequest, RejectManualPaymentCommand>(
request,
context);
}
[RequiresPermission(PermissionNames.ManualPaymentsView)]
public override async Task<GetManualPaymentsResponse> GetManualPayments(
GetManualPaymentsRequest request,
ServerCallContext context)
{
var query = new GetManualPaymentsQuery
{
PageNumber = request.PageNumber,
PageSize = request.PageSize,
UserId = request.UserId?.Value,
Status = request.Status?.Value,
Type = request.Type?.Value,
ReferenceNumber = request.ReferenceNumber?.Value,
// RequestedBy و OrderByDescending در این نسخه از UI ارسال نمی‌شود
};
var result = await _sender.Send(query, context.CancellationToken);
return result.Adapt<GetManualPaymentsResponse>();
}
}

View File

@@ -19,47 +19,62 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase
{
_dispatchRequestToCQRS = dispatchRequestToCQRS;
}
[RequiresPermission(PermissionNames.OrdersCreate)]
public override async Task<CreateNewUserOrderResponse> CreateNewUserOrder(CreateNewUserOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CreateNewUserOrderRequest, CreateNewUserOrderCommand, CreateNewUserOrderResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersUpdate)]
public override async Task<Empty> UpdateUserOrder(UpdateUserOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateUserOrderRequest, UpdateUserOrderCommand>(request, context);
}
[RequiresPermission(PermissionNames.OrdersDelete)]
public override async Task<Empty> DeleteUserOrder(DeleteUserOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteUserOrderRequest, DeleteUserOrderCommand>(request, context);
}
[RequiresPermission(PermissionNames.OrdersView)]
public override async Task<GetUserOrderResponse> GetUserOrder(GetUserOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetUserOrderRequest, GetUserOrderQuery, GetUserOrderResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersView)]
public override async Task<GetAllUserOrderByFilterResponse> GetAllUserOrderByFilter(GetAllUserOrderByFilterRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetAllUserOrderByFilterRequest, GetAllUserOrderByFilterQuery, GetAllUserOrderByFilterResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersUpdate)]
public override async Task<UpdateOrderStatusResponse> UpdateOrderStatus(UpdateOrderStatusRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<UpdateOrderStatusRequest, UpdateOrderStatusCommand, UpdateOrderStatusResponse>(request, context);
}
[RequiresPermission(PermissionNames.ReportsView)]
public override async Task<GetOrdersByDateRangeResponse> GetOrdersByDateRange(GetOrdersByDateRangeRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<GetOrdersByDateRangeRequest, GetOrdersByDateRangeQuery, GetOrdersByDateRangeResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersUpdate)]
public override async Task<ApplyDiscountToOrderResponse> ApplyDiscountToOrder(ApplyDiscountToOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ApplyDiscountToOrderRequest, ApplyDiscountToOrderCommand, ApplyDiscountToOrderResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersView)]
public override async Task<CalculateOrderPVResponse> CalculateOrderPV(CalculateOrderPVRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CalculateOrderPVRequest, CalculateOrderPVQuery, CalculateOrderPVResponse>(request, context);
}
[RequiresPermission(PermissionNames.OrdersCancel)]
public override async Task<CancelOrderResponse> CancelOrder(CancelOrderRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<CancelOrderRequest, CancelOrderCommand, CancelOrderResponse>(request, context);