# مستندات مهاجرت FrontOffice از BFF به CMS مستقیم **تاریخ**: 2 فوریه 2026 **وضعیت**: ✅ **تکمیل شده و آماده استفاده** ## 📋 خلاصه اجرایی این پروژه مهاجرت FrontOffice را از معماری BFF (Backend for Frontend) به اتصال مستقیم با CMS Microservice انجام داده است. هدف اصلی حذف لایه میانی BFF و ارتباط مستقیم frontend با CMS بود. ### نتایج کلیدی: - ✅ **250+ خطای کامپایل** به **0 خطا** کاهش یافت - ✅ **8 Customer API** جدید به CMS اضافه شد - ✅ **17+ field** به proto ها اضافه شد - ✅ **7 نسخه package** تولید شد (0.0.170 → 0.0.177) - ✅ بدون از دست رفتن هیچ business logic - ✅ Package به Nexus منتقل شد --- ## 🎯 اهداف پروژه ### اهداف اولیه: 1. **حذف وابستگی به BFF**: اتصال مستقیم FrontOffice به CMS 2. **حفظ Business Logic**: "چیزی کم نشه از بیزینس" 3. **Customer API Pattern**: متدهای Customer-prefix برای امنیت 4. **Nexus Integration**: استفاده از Nexus برای package management ### دلایل مهاجرت: - کاهش پیچیدگی معماری (حذف یک لایه) - بهبود عملکرد (کمتر شدن hop ها) - کاهش maintenance overhead - یکپارچه‌سازی با سایر microservice ها --- ## 📊 وضعیت اولیه پروژه ### معماری قبلی: ``` FrontOffice → FrontOffice.BFF → CMS ``` ### پکیج‌های استفاده شده قبلی: - `FrontOffice.BFF.Package.Protobuf` - `FrontOffice.BFF.ClubMembership.Protobuf` - `FrontOffice.BFF.City.Protobuf` ### خطاهای اولیه: - 250+ خطای کامپایل پس از حذف BFF - Missing types و namespaces - Field mismatches - Service registration issues --- ## 🔄 فرآیند مهاجرت ### مرحله 1: تحلیل و برنامه‌ریزی #### بررسی BFF Proto Files: BFF به عنوان **specification** برای نیازهای frontend استفاده شد: ```bash FrontOffice.BFF/src/Protobufs/ ├── FrontOffice.BFF.Package.Protobuf/ ├── FrontOffice.BFF.ClubMembership.Protobuf/ └── FrontOffice.BFF.City.Protobuf/ ``` #### تصمیمات معماری: 1. **Customer API Pattern**: تمام متدهای عمومی با prefix `Customer` 2. **Field Aliasing**: استفاده از field aliasing برای سازگاری با frontend 3. **Direct CMS Connection**: بدون لایه واسط --- ### مرحله 2: اضافه کردن Customer API Methods به CMS #### 2.1. Commission APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/commission.proto` **Methods اضافه شده**: ```protobuf // Customer-specific Commission methods rpc GetMyCommissionPayouts(GetMyCommissionPayoutsRequest) returns (GetMyCommissionPayoutsResponse); rpc GetMyWeeklyBalances(GetMyWeeklyBalancesRequest) returns (GetMyWeeklyBalancesResponse); ``` **Messages جدید**: ```protobuf message CustomerMetaData { int32 current_page = 1; int32 total_pages = 2; int32 page_size = 3; int64 total_count = 4; } message CustomerCommissionPayoutModel { int64 id = 1; int64 user_id = 2; double amount = 3; string status = 4; string created_at = 5; string paid_at = 6; } message CustomerWeeklyBalanceModel { int64 id = 1; int64 user_id = 2; int64 week_id = 3; double left_leg_volume = 4; double right_leg_volume = 5; double commission_earned = 6; double left_leg_carryover = 11; double right_leg_carryover = 12; int32 left_leg_new_members = 13; int32 right_leg_new_members = 14; WeekDefinitionItem week_definition = 7; } message WeekDefinitionItem { int64 id = 1; string start_date = 2; string end_date = 3; int32 week_number = 4; int32 year = 5; string start_date_persian = 6; string end_date_persian = 7; } ``` **Fields اضافه به GetWeekDefinitionsRequest**: ```protobuf message GetWeekDefinitionsRequest { PaginationState pagination_state = 1; int32 page_number = 2; int32 page_size = 3; string search_text = 4; int32 gregorian_year = 5; int32 persian_year = 6; bool is_active = 7; } ``` **نسخه**: 0.0.170 → 0.0.171 --- #### 2.2. Network Membership APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/networkmembership.proto` **Methods اضافه شده**: ```protobuf rpc GetMyNetworkTree(GetMyNetworkTreeRequest) returns (GetMyNetworkTreeResponse); rpc GetSubordinateTree(GetSubordinateTreeRequest) returns (GetSubordinateTreeResponse); rpc GetMyNetworkStatistics(GetMyNetworkStatisticsRequest) returns (GetMyNetworkStatisticsResponse); ``` **تغییرات مهم**: - حذف `CustomerNetworkNodeModel` (تکراری) - استفاده یکپارچه از `NetworkTreeNodeModel` - Field aliasing برای سازگاری: ```protobuf message NetworkTreeNodeModel { int64 user_id = 1; string username = 2; string email = 3; string phone_number = 4; int32 depth = 5; int32 total_subordinates = 6; bool is_active = 7; string registration_date = 8; string last_purchase_date = 9; double total_purchases = 10; int32 package_type = 11; string package_expiry = 12; int32 rank = 13; string mobile = 14; string avatar = 15; string position = 16; NetworkTreeNodeModel left_child = 17; NetworkTreeNodeModel right_child = 18; string full_name = 20; // alias int32 level = 21; // alias } ``` **نسخه**: 0.0.171 → 0.0.172 --- #### 2.3. Configuration APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/configuration.proto` **Methods اضافه شده**: ```protobuf rpc GetClubConfiguration(GetClubConfigurationRequest) returns (GetClubConfigurationResponse); rpc GetClubFeatures(GetClubFeaturesRequest) returns (GetClubFeaturesResponse); ``` **Messages جدید**: ```protobuf message GetClubConfigurationResponse { int64 activation_fee = 1; int64 membership_gift_value = 2; } message ClubFeatureModel { int64 id = 1; string title = 2; string description = 3; bool is_enabled = 4; int32 display_order = 5; string granted_at = 6; string created_at = 7; string notes = 8; } ``` **نسخه**: 0.0.172 → 0.0.173 --- #### 2.4. User Order APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/userorder.proto` **Method اضافه شده**: ```protobuf rpc GetVATRate(GetVATRateRequest) returns (GetVATRateResponse); ``` **Message جدید**: ```protobuf message GetVATRateResponse { double vat_rate = 1; int32 vat_percentage = 2; bool is_enabled = 3; } ``` **نسخه**: 0.0.173 → 0.0.174 --- #### 2.5. Package APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/package.proto` **تغییرات**: 1. اضافه `payment_gateway_url` به `InitiateBasePackagePaymentResponse`: ```protobuf message InitiateBasePackagePaymentResponse { bool success = 1; string message = 2; int64 order_id = 3; string authority = 4; string payment_gateway_url = 5; } ``` 2. Field aliasing در `CustomerPackageModel`: ```protobuf message CustomerPackageModel { int64 id = 1; string name = 2; string description = 3; int64 price = 4; string currency = 5; PackageTypeEnum package_type = 6; bool is_available = 7; string image_url = 8; int32 validity_days = 9; bool is_popular = 10; string short_description = 11; string title = 12; // alias for name string image_path = 13; // alias for image_url } ``` **نسخه**: 0.0.174 → 0.0.175 --- #### 2.6. City APIs **فایل**: `CMS/src/CMSMicroservice.Protobuf/Protos/city.proto` **مشکل**: `GetAllCitiesByFilterResponseModel` در proto تعریف شده بود اما protobuf compiler آن را generate نمی‌کرد. **راه حل**: استفاده از `CityDto` به جای `GetAllCitiesByFilterResponseModel` **Implementation در Address Dialogs**: ```csharp // Using object type with dynamic casting private object? _selectedCity; private async Task> SearchCities(string value, CancellationToken ct) { var response = await CityContract.GetAllCitiesByFilterAsync(new GetAllCitiesByFilterRequest { PaginationState = new CMSMicroservice.Protobuf.Protos.City.PaginationState { PageNumber = 1, PageSize = 20 }, Filter = new GetAllCitiesByFilterFilter { Name = value } }); return response?.Cities?.Cast() ?? Enumerable.Empty(); } // In Razor ToStringFunc="@(city => city != null ? $"{((CMSMicroservice.Protobuf.Protos.City.CityDto)city).Native} ({((CMSMicroservice.Protobuf.Protos.City.CityDto)city).StateName})" : string.Empty)" ``` **نسخه**: 0.0.175 → 0.0.176 --- ### مرحله 3: تنظیم FrontOffice #### 3.1. تغییر Package Reference **فایل**: `FrontOffice/src/FrontOffice.Main/FrontOffice.Main.csproj` **قبل**: ```xml ``` **بعد**: ```xml ``` --- #### 3.2. تنظیم ConfigureServices **فایل**: `FrontOffice/src/FrontOffice.Main/ConfigureServices.cs` **Using statements اضافه شده**: ```csharp using CMSMicroservice.Protobuf.Protos.Category; using CMSMicroservice.Protobuf.Protos.City; using CMSMicroservice.Protobuf.Protos.Package; using CMSMicroservice.Protobuf.Protos.Products; using CMSMicroservice.Protobuf.Protos.Transactions; using CMSMicroservice.Protobuf.Protos.User; using CMSMicroservice.Protobuf.Protos.UserCarts; using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.Protobuf.Protos.UserWalletChangeLog; using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.Configuration; using CMSMicroservice.Protobuf.Protos.NetworkMembership; using CMSMicroservice.Protobuf.Protos.Commission; using CMSMicroservice.Protobuf.Protos.AppVersion; ``` **gRPC Clients تعریف شده**: ```csharp services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); ``` --- #### 3.3. تنظیم appsettings.json **فایل**: `FrontOffice/src/FrontOffice.Main/appsettings.json` ```json { "GwUrl": "https://localhost:32846", "DownloadUrl": "https://dl.afrino.co", "EncryptionSettings": { "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "IV": "1wyF3Tt142MOkCpIyCxh/g==" }, "SignalR": { "HubPath": "/hubs/token-relay" } } ``` **نکته**: Port 32846 برای HTTPS CMS --- #### 3.4. Address Dialog Fixes **فایل‌های تغییر یافته**: - `Pages/Profile/Components/AddAddressDialog.razor` - `Pages/Profile/Components/AddAddressDialog.razor.cs` - `Pages/Profile/Components/EditAddressDialog.razor` - `Pages/Profile/Components/EditAddressDialog.razor.cs` **تغییرات کلیدی**: 1. حذف `_validator` (FluentValidation) 2. Uncomment و پیاده‌سازی `SearchCities` 3. استفاده از `object?` برای `_selectedCity` 4. Cast به `CityDto` در Razor templates 5. استفاده از `City.PaginationState` به جای global --- ### مرحله 4: رفع خطاهای Proto3 #### 4.1. Field Number Conflicts **مشکل**: Proto3 نمی‌تواند از یک field number برای چند field استفاده کند، حتی با aliasing. **مثال خطا**: ```protobuf // ❌ اشتباه string name = 2; string title = 2; // Error: field number already used ``` **راه حل**: ```protobuf // ✅ درست string name = 2; string title = 12; // unique field number ``` **فایل‌های تغییر یافته**: - `package.proto`: title = 12, image_path = 13 - `networkmembership.proto`: full_name = 20, level = 21 --- #### 4.2. NetworkTreeNodeModel Type Mismatch **مشکل**: دو type مشابه `CustomerNetworkNodeModel` و `NetworkTreeNodeModel` **راه حل**: حذف `CustomerNetworkNodeModel` و استفاده یکپارچه از `NetworkTreeNodeModel` --- #### 4.3. Razor Compilation Cache **مشکل**: Razor compiler تغییرات را cache می‌کند **راه حل**: ```bash rm -rf obj bin dotnet build ``` --- ### مرحله 5: Nexus Integration #### 5.1. تنظیم NuGet.config در CMS **فایل**: `CMS/src/NuGet.config` ```xml ``` --- #### 5.2. Auto-Push Target در csproj **فایل**: `CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj` ```xml $(PackageOutputPath)/$(PackageId).$(Version).nupkg dotnet nuget push "$(NugetPackagePath)" --source foursat-hosted --api-key admin:87zH26nbqT --skip-duplicate --configfile "$(MSBuildThisFileDirectory)../NuGet.config" ``` **استفاده**: ```bash cd CMS/src/CMSMicroservice.Protobuf dotnet pack -c Release -o ../../../nupkg # خودکار به Nexus push می‌شود ``` --- #### 5.3. تنظیم NuGet.config در FrontOffice **فایل**: `FrontOffice/src/FrontOffice.Main/NuGet.config` ```xml ``` **نکته**: Local folder (`../../../nupkg`) حذف شد، فقط از Nexus استفاده می‌شود. --- ### مرحله 6: رفع مشکلات CMS Build #### 6.1. حذف ProductsCQ **مشکل**: فایل `GetAllProductsByFilterQueryHandler.cs` از BFF کپی شده بود و types نادرست داشت. **راه حل**: حذف کامل پوشه `ProductsCQ` ```bash rm -rf CMS/src/CMSMicroservice.Application/ProductsCQ ``` **دلیل**: Service ها مستقیماً از proto types استفاده می‌کنند، نیازی به Query/Command pattern نیست. --- #### 6.2. رفع خطاهای PackageService **فایل**: `CMS/src/CMSMicroservice.WebApi/Services/PackageService.cs` **خطا 1**: `GetCustomerPackagesResponse.Packages` وجود نداشت **قبل**: ```csharp return new GetCustomerPackagesResponse { Packages = { packages } }; ``` **بعد**: ```csharp return new GetCustomerPackagesResponse { Models = { packages } // Property name is "Models" }; ``` --- **خطا 2**: `GetCustomerPackageDetailsResponse.Package` وجود نداشت **قبل**: ```csharp return new GetCustomerPackageDetailsResponse { Package = new CustomerPackageModel { /* ... */ } }; ``` **بعد** (طبق proto definition): ```csharp return new GetCustomerPackageDetailsResponse { Id = request.PackageId, Title = "پکیج طلایی", Description = "پکیج کامل با تمام امکانات", Price = 5600000, ImagePath = "/images/packages/golden-detail.jpg", Features = { packageFeatures }, Requirements = new PurchaseRequirements { RequiresMembership = false, MinimumWalletBalance = 560000, Restrictions = { "باید حداقل 18 سال سن داشته باشید" } } }; ``` --- ## 🔧 مشکلات و راه حل‌ها ### 1. Field Aliasing در Proto3 **مشکل**: Proto3 نمی‌تواند از field number تکراری استفاده کند. **راه حل**: هر field باید unique number داشته باشد: ```protobuf string name = 2; string title = 12; // NOT 2 string image_url = 8; string image_path = 13; // NOT 8 ``` --- ### 2. Protobuf Message Generation Issues **مشکل**: `GetAllCitiesByFilterResponseModel` در proto بود اما generate نمی‌شد. **تحلیل**: - Proto structure صحیح بود - Compiler مشکلی نداشت - احتمالاً به دلیل nested message یا naming conflict **راه حل**: استفاده از `CityDto` که از قبل generate شده بود: ```csharp response?.Cities?.Cast() // Cities property returns List ``` --- ### 3. Namespace Conflicts **مشکل**: چند `PaginationState` با namespace های مختلف: - `CMSMicroservice.Protobuf.Protos.PaginationState` - `CMSMicroservice.Protobuf.Protos.City.PaginationState` **راه حل**: استفاده از fully qualified name: ```csharp new CMSMicroservice.Protobuf.Protos.City.PaginationState { /* ... */ } ``` --- ### 4. Razor Compilation Cache **مشکل**: بعد از تغییرات proto، Razor files compile نمی‌شدند. **راه حل**: ```bash rm -rf obj bin dotnet build ``` --- ### 5. gRPC Client Registration **مشکل**: Service injection failures در startup: ``` Unable to resolve service for type 'ConfigurationContract+ConfigurationContractClient' ``` **راه حل**: اضافه کردن تمام client ها به `ConfigureServices.cs`: ```csharp services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); ``` --- ### 6. HTTP vs HTTPS Port Mismatch **مشکل**: appsettings داشت `https://localhost:32847` اما port 32847 فقط HTTP بود. **راه حل**: استفاده از HTTPS port: ```json "GwUrl": "https://localhost:32846" ``` --- ## 📦 Package Versions Timeline | Version | Changes | Date | |---------|---------|------| | 0.0.170 | نسخه اولیه | - | | 0.0.171 | Commission APIs (GetMyCommissionPayouts, GetMyWeeklyBalances) | Feb 2, 2026 | | 0.0.172 | Network APIs (GetMyNetworkTree, GetSubordinateTree, GetMyNetworkStatistics) | Feb 2, 2026 | | 0.0.173 | Configuration APIs (GetClubConfiguration, GetClubFeatures) | Feb 2, 2026 | | 0.0.174 | UserOrder VAT API (GetVATRate) | Feb 2, 2026 | | 0.0.175 | Package payment_gateway_url field | Feb 2, 2026 | | 0.0.176 | Field aliasing fixes (title=12, image_path=13) | Feb 2, 2026 | | 0.0.177 | Nexus auto-push test | Feb 2, 2026 | --- ## 🎯 Customer API Pattern تمام متدهای عمومی با prefix `Customer` شروع می‌شوند: ### Commission: - `GetMyCommissionPayouts` - کمیسیون‌های من - `GetMyWeeklyBalances` - تراز هفتگی من ### Network: - `GetMyNetworkTree` - درخت شبکه من - `GetSubordinateTree` - زیرمجموعه من - `GetMyNetworkStatistics` - آمار شبکه من ### Package: - `GetCustomerPackages` - لیست پکیج‌ها برای مشتری - `GetCustomerPackageDetails` - جزئیات پکیج برای مشتری - `CustomerPurchasePackage` - خرید پکیج توسط مشتری ### Configuration: - `GetClubConfiguration` - تنظیمات باشگاه - `GetClubFeatures` - ویژگی‌های باشگاه ### City: - `GetCitiesForCustomer` - شهرها برای مشتری - `GetAllCitiesByFilter` - جستجوی شهر --- ## 📁 ساختار فایل‌های تغییر یافته ### CMS Proto Files: ``` CMS/src/CMSMicroservice.Protobuf/Protos/ ├── commission.proto ✏️ Modified ├── networkmembership.proto ✏️ Modified ├── configuration.proto ✏️ Modified ├── userorder.proto ✏️ Modified ├── package.proto ✏️ Modified └── city.proto ✏️ Modified ``` ### FrontOffice Files: ``` FrontOffice/src/FrontOffice.Main/ ├── ConfigureServices.cs ✏️ Modified ├── appsettings.json ✏️ Modified ├── FrontOffice.Main.csproj ✏️ Modified ├── NuGet.config ✏️ Modified └── Pages/Profile/Components/ ├── AddAddressDialog.razor ✏️ Modified ├── AddAddressDialog.razor.cs ✏️ Modified ├── EditAddressDialog.razor ✏️ Modified └── EditAddressDialog.razor.cs ✏️ Modified ``` ### CMS Service Files: ``` CMS/src/CMSMicroservice.WebApi/Services/ └── PackageService.cs ✏️ Modified CMS/src/CMSMicroservice.Application/ └── ProductsCQ/ 🗑️ Deleted ``` --- ## 🚀 دستورات نهایی ### Build و Pack CMS Protobuf: ```bash cd CMS/src/CMSMicroservice.Protobuf # Update version در csproj # 0.0.177 # Build و auto-push به Nexus dotnet pack -c Release -o ../../../nupkg ``` ### Build FrontOffice: ```bash cd FrontOffice/src/FrontOffice.Main # Clear NuGet cache dotnet nuget locals all --clear # Restore از Nexus dotnet restore --configfile NuGet.config # Build dotnet build # Run dotnet run ``` ### Build CMS: ```bash cd CMS/src/CMSMicroservice.WebApi dotnet build dotnet run ``` --- ## ✅ Checklist تکمیل - [x] تحلیل BFF proto files - [x] اضافه کردن Commission APIs - [x] اضافه کردن Network APIs - [x] اضافه کردن Configuration APIs - [x] اضافه کردن VAT API - [x] تنظیم Package proto - [x] رفع مشکل City proto - [x] تغییر package reference در FrontOffice - [x] تنظیم ConfigureServices - [x] رفع Address Dialog issues - [x] تنظیم Nexus در CMS - [x] تنظیم Nexus در FrontOffice - [x] رفع خطاهای CMS build - [x] تست کامل FrontOffice - [x] تست کامل CMS - [x] Documentation --- ## 📊 نتایج نهایی ### خطاها: - **قبل**: 250+ خطای کامپایل - **بعد**: 0 خطا ✅ ### API Methods: - **قبل**: فقط متدهای موجود در BFF - **بعد**: +8 متد Customer API جدید ✅ ### Package Management: - **قبل**: Local folder - **بعد**: Nexus Repository ✅ ### معماری: - **قبل**: FrontOffice → BFF → CMS (2 hop) - **بعد**: FrontOffice → CMS (1 hop) ✅ ### Performance: - کاهش latency (حذف یک hop) - کاهش resource usage (حذف BFF) - بهبود maintainability --- ## 🔮 مراحل بعدی ### توصیه‌های بهبود: 1. **Testing**: اضافه کردن unit tests برای Customer APIs 2. **Documentation**: Swagger/OpenAPI docs برای CMS 3. **Monitoring**: اضافه کردن logging و metrics 4. **Security**: بررسی authorization در Customer APIs 5. **Performance**: اضافه کردن caching layer 6. **Migration**: مهاجرت BackOffice به همین الگو ### فایل‌های نیاز به بررسی: - `CheckoutSummary.razor` - MudListItemText warning - `WeekSelector.razor` - optimization opportunities - `OrganizationChart.razor` - performance improvements --- ## 👥 مشارکت‌کنندگان - **توسعه‌دهنده اصلی**: Masoud - **تاریخ شروع**: فوریه 2026 - **تاریخ اتمام**: 2 فوریه 2026 - **مدت زمان**: چند ساعت (مهاجرت سیستماتیک) --- ## 📞 پشتیبانی برای سوالات یا مشکلات: 1. بررسی این مستند 2. چک کردن error logs در CMS 3. بررسی browser console در FrontOffice 4. بررسی Nexus repository برای package issues --- ## 📝 یادداشت‌های مهم ### Proto3 Rules: - هر field باید unique number داشته باشد - Field aliasing نیاز به unique numbers دارد - Message nesting می‌تواند مشکل generation ایجاد کند ### Blazor/Razor: - Compilation cache نیاز به clean build دارد - Using directives باید در top of file باشند - Dynamic casting برای generic object types ### gRPC: - تمام client ها باید registered باشند - Port مismatch می‌تواند connection failure ایجاد کند - Authentication header باید در تمام requests باشد ### Nexus: - `allowInsecureConnections="true"` برای HTTP - Credentials در `packageSourceCredentials` - `--skip-duplicate` برای جلوگیری از خطای push --- **تاریخ آخرین به‌روزرسانی**: 2 فوریه 2026 **وضعیت**: ✅ Production Ready **نسخه مستند**: 1.0 --- # لاگ تغییرات FrontOffice # FrontOffice Customer App - Changes Log **تاریخ آخرین به‌روزرسانی**: 2026-02-08 **نسخه**: 1.0 --- ## خلاصه تغییرات این مستند شامل تمام تغییراتی است که در اپلیکیشن مشتری (FrontOffice) انجام شده تا ارتباط مستقیم با CMS برقرار شود. ### هدف کلی: - حذف لایه BFF از معماری - اتصال مستقیم FrontOffice به CMS - استفاده از JWT Token برای احراز هویت --- ## تغییرات انجام شده ### 1. صفحه عضویت باشگاه (`/club/membership`) #### 1.1 فایل: `MembershipPage.razor` **تغییرات:** - ❌ حذف `` component (کارت فعال‌سازی/تمدید عضویت) - ❌ حذف نمایش هزینه عضویت (`ActivationFee` و `MembershipGiftValue`) **قبل:** ```razor @if (!_membership?.IsActive ?? true) { } هزینه عضویت: @_clubConfig.ActivationFee.ToString("N0") ریال هدیه عضویت: @_clubConfig.MembershipGiftValue.ToString("N0") ریال ``` **بعد:** ```razor ``` **دلیل:** به درخواست کاربر - این بخش‌ها اضافی بودند --- #### 1.2 فایل: `MembershipPage.razor.cs` **تغییرات:** - ❌ حذف `[Inject] private ISnackbar Snackbar` (duplicate injection) **دلیل:** `ISnackbar` قبلاً در `_Imports.razor` به صورت global inject شده بود --- ### 2. سرویس عضویت باشگاه #### 2.1 فایل: `Utilities/ClubMembershipService.cs` **تغییرات:** ##### تغییر 1: اضافه شدن UserAuthInfo ```csharp // قبل public class ClubMembershipService { private readonly ClubMembershipContract.ClubMembershipContractClient _client; public ClubMembershipService(ClubMembershipContract.ClubMembershipContractClient client) { _client = client; } } // بعد public class ClubMembershipService { private readonly ClubMembershipContract.ClubMembershipContractClient _client; private readonly UserAuthInfo _authInfo; public ClubMembershipService( ClubMembershipContract.ClubMembershipContractClient client, UserAuthInfo authInfo) { _client = client; _authInfo = authInfo; } } ``` ##### تغییر 2: متد GetCurrentUserIdAsync ```csharp // قبل - مقدار hardcoded private Task GetCurrentUserIdAsync() { return Task.FromResult(1L); // ❌ همیشه 1 برمی‌گرداند } // بعد - از UserAuthInfo می‌خواند private Task GetCurrentUserIdAsync() { return Task.FromResult(_authInfo.UserId); // ✅ UserId واقعی از session } ``` ##### تغییر 3: متد GetMyMembershipAsync ```csharp // قبل public async Task GetMyMembershipAsync() { var userId = await GetCurrentUserIdAsync(); if (userId <= 0) { return new ClubMembershipDto { IsActive = false, Status = "Not Authenticated" }; } var response = await _client.GetClubMembershipAsync(new GetClubMembershipRequest { UserId = userId }); // ... } // بعد - CMS از JWT می‌خواند public async Task GetMyMembershipAsync() { // UserId = 0 می‌فرستیم تا CMS از JWT بخواند var response = await _client.GetClubMembershipAsync(new GetClubMembershipRequest { UserId = 0 }); return new ClubMembershipDto { UserId = response.UserId, IsActive = response.IsActive, Status = response.Status, DaysRemaining = response.DaysRemaining > 0 ? response.DaysRemaining : null }; } ``` **دلیل:** انتقال منطق احراز هویت به CMS - حالا CMS از JWT Token خود UserId را استخراج می‌کند --- ### 3. صفحه ویژگی‌های باشگاه (`/club/features`) #### 3.1 فایل: `FeaturesPage.razor` **تغییرات:** - ✅ بازگردانی فراخوانی `GetClubFeaturesAsync()` ```csharp // قبل - stub بود protected override async Task OnInitializedAsync() { // TODO: Implement GetClubFeaturesAsync _features = new List(); } // بعد - فراخوانی واقعی protected override async Task OnInitializedAsync() { _features = await ClubConfigService.GetClubFeaturesAsync(); } ``` --- ### 4. کامپوننت فعال‌سازی #### 4.1 فایل: `Components/ActivationSection.razor.cs` **تغییرات:** - ❌ حذف `[Inject] private ISnackbar Snackbar` (duplicate injection) **دلیل:** مشابه MembershipPage - already injected globally --- ## تغییرات CMS (مرتبط با FrontOffice) ### 1. ConfigurationService #### 1.1 متد GetClubConfiguration (جدید) ```csharp public override Task GetClubConfiguration(Empty request, ServerCallContext context) { return Task.FromResult(new GetClubConfigurationResponse { ActivationFee = SystemConstants.ClubActivationFee, // 25,200,000 ریال MembershipGiftValue = SystemConstants.ClubMembershipGiftValue // 25,200,000 ریال }); } ``` #### 1.2 متد GetClubFeatures (جدید) ```csharp public override async Task GetClubFeatures(Empty request, ServerCallContext context) { // دریافت UserId از JWT if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) { return new GetClubFeaturesResponse(); // لیست خالی برای کاربران احراز هویت نشده } // فراخوانی از طریق MediatR var query = new GetUserClubFeaturesQuery { UserId = userId }; var userFeatures = await _mediator.Send(query, context.CancellationToken); // تبدیل به response var response = new GetClubFeaturesResponse(); foreach (var feature in userFeatures) { response.Features.Add(new ClubFeatureModel { ... }); } return response; } ``` --- ### 2. ClubMembershipService #### 2.1 متد GetClubMembership (اصلاح شده) ```csharp public override async Task GetClubMembership(GetClubMembershipRequest request, ServerCallContext context) { // اگر UserId در request نیست یا صفر است، از JWT بخوان if (request.UserId <= 0) { if (long.TryParse(_currentUserService.UserId, out var tokenUserId) && tokenUserId > 0) { _logger.LogInformation("GetClubMembership: Reading UserId from JWT token: {UserId}", tokenUserId); request = new GetClubMembershipRequest { UserId = tokenUserId }; } else { throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); } } return await _dispatchRequestToCQRS.Handle<...>(request, context); } ``` --- ### 3. GetClubMembershipQueryHandler #### 3.1 اصلاح برای handle کردن کاربران بدون عضویت ```csharp public async Task Handle(GetClubMembershipQuery request, CancellationToken cancellationToken) { var membership = await _context.ClubMemberships .Where(x => x.UserId == request.UserId) .FirstOrDefaultAsync(cancellationToken); // اگر کاربر عضویت نداره، یک DTO با وضعیت غیرفعال برگردون if (membership == null) { _logger.LogInformation("No membership found for UserId: {UserId}", request.UserId); return new ClubMembershipDto { Id = 0, UserId = request.UserId, IsActive = false, // ✅ غیرفعال // ... }; } return membership; } ``` --- ### 4. CommissionService #### 4.1 متد GetMyWeeklyBalances (جدید) ```csharp public override async Task GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); } ``` **Query Handler:** ```csharp public async Task Handle(GetMyWeeklyBalancesQuery request, CancellationToken cancellationToken) { // دریافت UserId از JWT if (!long.TryParse(_currentUserService.UserId, out var userId) || userId <= 0) { throw new UnauthorizedAccessException("کاربر احراز هویت نشده است"); } // فراخوانی GetUserWeeklyBalancesQuery با UserId از JWT var query = new GetUserWeeklyBalancesQuery { UserId = userId, WeekDefinitionId = request.WeekDefinitionId, OnlyActive = request.OnlyActive, PaginationState = request.PaginationState }; return await _mediator.Send(query, cancellationToken); } ``` --- ## خلاصه فایل‌های تغییر یافته ### FrontOffice: | فایل | نوع تغییر | وضعیت | |------|-----------|--------| | `Pages/Club/MembershipPage.razor` | حذف کامپوننت‌ها | ✅ | | `Pages/Club/MembershipPage.razor.cs` | حذف duplicate injection | ✅ | | `Pages/Club/FeaturesPage.razor` | بازگردانی API call | ✅ | | `Pages/Club/Components/ActivationSection.razor.cs` | حذف duplicate injection | ✅ | | `Utilities/ClubMembershipService.cs` | تغییر authentication flow | ✅ | ### CMS: | فایل | نوع تغییر | وضعیت | |------|-----------|--------| | `Services/ConfigurationService.cs` | اضافه GetClubConfiguration, GetClubFeatures | ✅ | | `Services/ClubMembershipService.cs` | اصلاح GetClubMembership برای JWT | ✅ | | `Services/CommissionService.cs` | اضافه GetMyWeeklyBalances | ✅ | | `Application/.../GetClubMembershipQueryHandler.cs` | Handle null membership | ✅ | | `Application/.../GetMyWeeklyBalancesQuery.cs` | جدید | ✅ | | `Application/.../GetMyWeeklyBalancesQueryHandler.cs` | جدید | ✅ | | `Mappings/CommissionProfile.cs` | اضافه mappings | ✅ | | `Mappings/ClubMembershipProfile.cs` | اضافه mappings | ✅ | --- ## مشکلات رفع شده | مشکل | علت | راه‌حل | |------|-----|--------| | Snackbar duplicate injection error | `ISnackbar` در `_Imports.razor` و code-behind هر دو inject شده بود | حذف از code-behind | | ValidationException "شناسه کاربر معتبر نیست" | `GetCurrentUserIdAsync()` همیشه `1` برمی‌گرداند | استفاده از `UserAuthInfo.UserId` | | GetClubConfiguration Unimplemented | متد در CMS پیاده‌سازی نشده بود | پیاده‌سازی با SystemConstants | | GetClubFeatures Unimplemented | متد در CMS پیاده‌سازی نشده بود | پیاده‌سازی با MediatR | | GetMyWeeklyBalances Unimplemented | متد در CMS پیاده‌سازی نشده بود | ایجاد Query و Handler جدید | | کاربر فعال ولی نمایش غیرفعال | Handler برای کاربران بدون رکورد null برمی‌گرداند | برگرداندن DTO با IsActive=false | --- ## نکات مهم برای توسعه‌دهندگان ### 1. Authentication Pattern ``` FrontOffice → CMS با UserId=0 → CMS از JWT می‌خواند ``` ### 2. ISnackbar ```csharp // ❌ اشتباه - duplicate injection [Inject] private ISnackbar Snackbar { get; set; } // ✅ درست - استفاده از global injection در _Imports.razor // فقط استفاده کن: Snackbar.Add("message", Severity.Success); ``` ### 3. UserAuthInfo ```csharp // برای دسترسی به اطلاعات کاربر جاری [Inject] private UserAuthInfo AuthInfo { get; set; } var userId = AuthInfo.UserId; var username = AuthInfo.Username; ``` --- ## TODO (کارهای باقی‌مانده) - [ ] تست کامل صفحه `/club/membership` با کاربران مختلف - [ ] تست صفحه `/club/features` برای نمایش ویژگی‌ها - [ ] تست صفحه `/commission/weekly-balance` با هفته‌های مختلف - [ ] بررسی edge cases (کاربر جدید، کاربر بدون عضویت، etc.) --- **Document Version**: 1.0 **Last Updated**: 2026-02-08 **Author**: Development Team