Files
docs/cms/ADMIN-CUSTOMER-SEPARATION-FIX.md
T

6.9 KiB

🔀 جداسازی سرویس‌های Admin و Customer

آخرین بروزرسانی: February 10, 2026
مرتبط با: ICURRENTUSERSERVICE-IMPLEMENTATION.md


🐛 مشکل

پنل ادمین BackOffice بجای نمایش اطلاعات همه کاربران، فقط اطلاعات خود ادمین رو نشان میداد.

علت ریشه‌ای:

Query Handler ها وقتی UserId = 0 دریافت می‌کردند، بجای اینکه "همه کاربران" رو برگردانند، به JWT fallback می‌کردند و UserId ادمین رو از توکن استخراج می‌کردند:

// ❌ الگوی قدیمی (مشکل‌دار)
var userId = request.UserId == 0
    ? (long.TryParse(_currentUser.UserId, out var uid) ? uid : 0)  // ← fallback به JWT
    : request.UserId;

مشکل:

  • BackOffice (Admin)UserId = 0 ارسال میکنه → Handler از JWT ادمین میخونه → فقط اطلاعات ادمین برمیگرده
  • FrontOffice (Customer)UserId = 0 ارسال میکنه → Handler از JWT مشتری میخونه → اتفاقاً درسته، ولی دلیلش اشتباهه

الگوی جدید

اصل طراحی:

Handler ها بی‌خبر از JWT هستند. وظیفه resolve کردن کاربر، به عهده Service Layer (gRPC endpoint) است.

الگوی Handler:

// ✅ الگوی جدید
// UserId = 0 → بدون فیلتر (نمایش همه) — مناسب Admin
// UserId > 0 → فیلتر بر اساس کاربر خاص — مناسب Customer یا Admin

public async Task<Result> Handle(SomeQuery request, CancellationToken ct)
{
    var userId = request.UserId;
    
    var query = _context.SomeEntity.AsNoTracking();
    
    if (userId > 0)
        query = query.Where(x => x.UserId == userId);
    
    // userId == 0 → no filter → return all
    return await query.ToListAsync(ct);
}

الگوی Customer Service (JWT رو خودش resolve میکنه):

// ✅ Customer endpoint → حتماً JWT resolve میکنه
public override async Task<Response> GetMyData(Request request, ServerCallContext context)
{
    if (!long.TryParse(_currentUserService.UserId, out var userId) || userId == 0)
        throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated"));
    
    var query = new GetDataQuery { UserId = userId };  // ← userId صریح
    var result = await _sender.Send(query, context.CancellationToken);
    return MapToResponse(result);
}

الگوی Admin Service (UserId رو از request میگیره):

// ✅ Admin endpoint → UserId از request (0 = همه)
public override async Task<Response> GetAllData(Request request, ServerCallContext context)
{
    // request.UserId = 0 → handler همه رو برمیگردونه
    // request.UserId > 0 → handler فیلتر میکنه
    var result = await _dispatcher.Send(request, context);
    return result;
}

📝 لیست تغییرات

🔧 ۸ Query Handler اصلاح‌شده:

# Handler تغییر رفتار UserId = 0
1 GetCustomerOrdersQueryHandler حذف ICurrentUserService + JWT fallback بدون فیلتر → همه سفارشات
2 GetCustomerOrderQueryHandler حذف ICurrentUserService + JWT fallback هر سفارشی با OrderId
3 GetUserWeeklyBalancesQueryHandler حذف ICurrentUserService + JWT fallback بدون فیلتر → همه تعادل‌ها
4 GetUserCommissionPayoutsQueryHandler حذف ICurrentUserService + JWT fallback بدون فیلتر → همه پرداخت‌ها
5 GetNetworkStatisticsQueryHandler حذف ICurrentUserService + JWT fallback آمار root user (کل شبکه)
6 GetNetworkTreeQueryHandler حذف JWT fallback + خطا اگر UserId نباشد ArgumentException (الزامی)
7 GetUserQueryHandler حذف JWT fallback + خطا اگر UserId نباشد ArgumentException (الزامی)
8 GetUserWalletQueryHandler حذف JWT fallback + خطا اگر UserId نباشد ArgumentException (الزامی)

🌐 ۴ Customer Service Endpoint اصلاح‌شده:

# Service / Method تغییر
1 UserOrderService.GetCustomerOrders JWT resolve → ارسال customerUserId به handler
2 UserOrderService.GetCustomerOrder JWT resolve → ارسال customerUserId به handler
3 NetworkMembershipService.GetMyNetworkStatistics افزودن ICurrentUserService + JWT resolve
4 UserWalletService.GetCustomerWallet تغییر از Id = 0 به Id = userId (از JWT)

📐 دیاگرام جریان

درخواست Admin (BackOffice):

BackOffice Panel → gRPC (UserId=0) → Admin Service → Handler (UserId=0 → no filter → ALL users) ✅
BackOffice Panel → gRPC (UserId=42) → Admin Service → Handler (UserId=42 → filter → one user) ✅

درخواست Customer (FrontOffice):

FrontOffice App → gRPC → Customer Service → JWT resolve (UserId=42) → Handler (UserId=42 → filter) ✅

⚠️ نکات مهم

  1. Handler ها هرگز ICurrentUserService رو inject نمیکنند (بعد از این فیکس)
  2. فقط Customer Service endpoints مسئول JWT resolve هستند
  3. Admin endpoints از IDispatchRequestToCQRS استفاده میکنند و UserId مستقیم از proto request میاد
  4. Handler هایی که UserId الزامی دارند (مثل GetUser, GetUserWallet, GetNetworkTree) → ArgumentException پرتاب میکنند
  5. Handler هایی که لیست برمیگردونند (مثل GetCustomerOrders, GetWeeklyBalances) → UserId = 0 یعنی "بدون فیلتر"

🔗 فایل‌های تغییر‌یافته

Application Layer:

CMS/src/CMSMicroservice.Application/
├── OrdersCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs
├── OrdersCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs
├── UserWeeklyBalanceCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs
├── CommissionPayoutCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs
├── NetworkStatisticsCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs
├── NetworkTreeCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs
├── UserCQ/Queries/GetUser/GetUserQueryHandler.cs
└── UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs

WebApi Layer:

CMS/src/CMSMicroservice.WebApi/Services/
├── UserOrderService.cs          (GetCustomerOrders + GetCustomerOrder)
├── NetworkMembershipService.cs  (GetMyNetworkStatistics)
└── UserWalletService.cs         (GetCustomerWallet)