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

154 lines
6.9 KiB
Markdown

# 🔀 جداسازی سرویس‌های Admin و Customer
> آخرین بروزرسانی: February 10, 2026
> مرتبط با: [ICURRENTUSERSERVICE-IMPLEMENTATION.md](ICURRENTUSERSERVICE-IMPLEMENTATION.md)
---
## 🐛 مشکل
پنل ادمین BackOffice بجای نمایش اطلاعات **همه کاربران**، فقط اطلاعات **خود ادمین** رو نشان میداد.
### علت ریشه‌ای:
Query Handler ها وقتی `UserId = 0` دریافت می‌کردند، بجای اینکه "همه کاربران" رو برگردانند، به JWT fallback می‌کردند و UserId ادمین رو از توکن استخراج می‌کردند:
```csharp
// ❌ الگوی قدیمی (مشکل‌دار)
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:
```csharp
// ✅ الگوی جدید
// 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 میکنه):
```csharp
// ✅ 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 میگیره):
```csharp
// ✅ 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)
```