8.7 KiB
🔐 فیکس فلوی ثبتنام / ورود FrontOffice
تاریخ: بهمن ۱۴۰۴ (February 2026)
📋 خلاصه
بررسی کامل فلوی ثبتنام و ورود FrontOffice از UI تا دیتابیس انجام شد. ۳ باگ بحرانی شناسایی و رفع شده:
| # | شدت | مشکل | فایل |
|---|---|---|---|
| 1 | 🔴 بحرانی | کاربران جدید ثبتنام نمیشوند | UserCQ/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs |
| 2 | 🔴 بحرانی | امضای قرارداد همیشه شکست میخورد | UserCQ/AcceptContract/AcceptContractCommandHandler.cs |
| 3 | 🟡 متوسط | منوی کناری وضعیت نادرست نشان میدهد | FrontOffice.Main/Utilities/AuthService.cs |
🔴 باگ ۱ — کاربران جدید ثبتنام نمیشوند
مشکل
FrontOffice از UserContract.UserContractClient (user.proto) استفاده میکند → UserCQ/VerifyOtpTokenCommandHandler. این handler وقتی کاربر یافت نمیشد فقط خطای «کاربر یافت نشد» برمیگرداند و کاربر جدید ایجاد نمیکرد.
لاجیک ایجاد کاربر (شامل: اعتبارسنجی کد معرف، درخت باینری، موقعیت شاخه) در OtpTokenCQ/VerifyOtpTokenCommandHandler بود — سرویسی که FrontOffice اصلاً از آن استفاده نمیکند.
رفع
اضافه شدن لاجیک کامل ایجاد کاربر جدید به UserCQ/VerifyOtpTokenCommandHandler:
if (user == null)
{
// ۱. اعتبارسنجی کد معرف (ParentReferralCode) — الزامی
// ۲. بررسی وجود معرف و فعال بودن عضویت باشگاه
// ۳. بررسی ظرفیت (حداکثر ۲ زیرمجموعه مستقیم)
// ۴. تعیین شاخه (چپ اول، بعد راست)
// ۵. ایجاد User + UserRole + UserWallet
// ۶. رویدادهای دامنه: CreateNewUserEvent, CreateNewUserRoleEvent, CreateNewUserWalletEvent
// ۷. بارگذاری مجدد کاربر با روابط کامل
// ۸. تولید JWT token
}
اعتبارسنجیها
| مرحله | شرط | پیام خطا |
|---|---|---|
| کد معرف | خالی یا null | «کد معرف الزامی است» |
| معرف | وجود نداشته باشد | «معرف وجود ندارد» |
| عضویت باشگاه | غیرفعال باشد | «لینک دعوت معرف فعال نیست» |
| ظرفیت | بیش از ۱ فرزند | «ظرفیت معرف تکمیل است» |
| شاخه | هر دو پُر باشند | «ظرفیت معرف تکمیل است» |
فایل
CMS/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs
🔴 باگ ۲ — امضای قرارداد همیشه شکست میخورد
مشکل
AcceptContractCommandHandler از _currentUserService.Username برای پیدا کردن OTP و کاربر بر اساس شماره موبایل استفاده میکرد:
// ❌ قبل — Username = "{FirstName} {LastName}" نه شماره موبایل!
var otpToken = await _context.OtpTokens
.Where(x => x.Mobile == _currentUserService.Username ...)
var user = await _context.Users
.Where(x => x.Mobile == _currentUserService.Username ...)
CurrentUserService.Username مقدار ClaimTypes.Name را برمیگرداند که در JWT به صورت "{FirstName} {LastName}" ذخیره شده — نه شماره موبایل!
رفع
اول کاربر بر اساس UserId (از ClaimTypes.NameIdentifier) پیدا شود، سپس از user.Mobile برای جستجوی OTP استفاده شود:
// ✅ بعد — ابتدا کاربر از UserId پیدا شود
var userId = long.Parse(_currentUserService.UserId);
var user = await _context.Users
.Where(x => x.Id == userId)...
var otpToken = await _context.OtpTokens
.Where(x => x.Mobile == user.Mobile ...)
فایل
CMS/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs
🟡 باگ ۳ — IsCompleteRegister() داده قدیمی میخواند
مشکل
متد sync در AuthService:
// ❌ قبل — GetAwaiter() بدون GetResult() عملیات async را اجرا نمیکند
InitUserAuthInfo().GetAwaiter();
GetAwaiter() فقط یک شیء awaiter برمیگرداند ولی عملیات را اجرا نمیکند. در نتیجه _userAuthInfo مقداردهی نمیشود و منوی کناری (MainLayout.razor) وضعیت نادرست نشان میدهد.
رفع
// ✅ بعد — عملیات async را همگام اجرا میکند
InitUserAuthInfo().GetAwaiter().GetResult();
محل استفاده
MainLayout.razor خطوط ۱۰۶ و ۱۰۹:
Disabled="@(!AuthService.IsCompleteRegister())"
فایل
FrontOffice/src/FrontOffice.Main/Utilities/AuthService.cs
🏗️ معماری فلوی ثبتنام (بعد از فیکس)
┌─────────────────────────────────────────────────────────────┐
│ FrontOffice (Blazor WASM) │
│ │
│ LoginPage → SendOtp → VerifyOtp(mobile, code, referral) │
│ ↓ gRPC-Web │
├─────────────────────────────────────────────────────────────┤
│ CMS Backend (gRPC) │
│ │
│ UserContract.VerifyOtpToken │
│ ↓ │
│ UserCQ/VerifyOtpTokenCommandHandler │
│ │ │
│ ├── OTP صحیح؟ → ❌ خطا │
│ │ │
│ ├── کاربر موجود؟ → ✅ تولید JWT Token │
│ │ │
│ └── کاربر جدید؟ │
│ ├── اعتبارسنجی کد معرف │
│ ├── بررسی ظرفیت درخت باینری │
│ ├── ایجاد User + UserRole + UserWallet │
│ ├── رویدادهای دامنه │
│ └── تولید JWT Token │
│ │
│ بعد از ثبتنام → RegisterWizard: │
│ Step 1: اطلاعات شخصی │
│ Step 2: امضای قرارداد (AcceptContract + OTP) │
│ Step 3: خرید پکیج │
├─────────────────────────────────────────────────────────────┤
│ Database │
│ │
│ Users ─── UserRoles ─── UserWallets │
│ └── NetworkParentId, LegPosition (Binary Tree) │
│ └── UserContracts, ClubMembership │
└─────────────────────────────────────────────────────────────┘
📝 فایلهای تغییر یافته
| فایل | تغییر |
|---|---|
CMS/.../UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs |
اضافه شدن لاجیک ایجاد کاربر جدید (86→165 خط) |
CMS/.../UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs |
تغییر lookup از Username به UserId |
FrontOffice/.../Utilities/AuthService.cs |
اضافه شدن .GetResult() به GetAwaiter() |
✅ بیلد
CMS: 0 Error(s) ✅
FrontOffice: 0 Error(s) ✅
BackOffice: 0 Error(s) ✅