1313 lines
39 KiB
Markdown
1313 lines
39 KiB
Markdown
# مستندات مهاجرت 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<IEnumerable<object>> 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<object>() ?? Enumerable.Empty<object>();
|
|
}
|
|
|
|
// 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
|
|
<PackageReference Include="FrontOffice.BFF.Package.Protobuf" Version="x.x.x" />
|
|
<PackageReference Include="FrontOffice.BFF.ClubMembership.Protobuf" Version="x.x.x" />
|
|
<PackageReference Include="FrontOffice.BFF.City.Protobuf" Version="x.x.x" />
|
|
```
|
|
|
|
**بعد**:
|
|
```xml
|
|
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.176" />
|
|
```
|
|
|
|
---
|
|
|
|
#### 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<PackageContract.PackageContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserContract.UserContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserOrderContract.UserOrderContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserWalletContract.UserWalletContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<CategoryContract.CategoryContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<ProductsContract.ProductsContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<TransactionsContract.TransactionsContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserCartsContract.UserCartsContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserAddressContract.UserAddressContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserWalletChangeLogContract.UserWalletChangeLogContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<ClubMembershipContract.ClubMembershipContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<OtpTokenContract.OtpTokenContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<ConfigurationContract.ConfigurationContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
|
|
```
|
|
|
|
---
|
|
|
|
#### 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
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<configuration>
|
|
<packageSources>
|
|
<clear />
|
|
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json"
|
|
allowInsecureConnections="true" />
|
|
<add key="foursat-hosted" value="http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json"
|
|
allowInsecureConnections="true" />
|
|
</packageSources>
|
|
<packageSourceCredentials>
|
|
<Nexus>
|
|
<add key="Username" value="admin" />
|
|
<add key="ClearTextPassword" value="87zH26nbqT" />
|
|
</Nexus>
|
|
<foursat-hosted>
|
|
<add key="Username" value="admin" />
|
|
<add key="ClearTextPassword" value="87zH26nbqT" />
|
|
</foursat-hosted>
|
|
</packageSourceCredentials>
|
|
</configuration>
|
|
```
|
|
|
|
---
|
|
|
|
#### 5.2. Auto-Push Target در csproj
|
|
**فایل**: `CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj`
|
|
|
|
```xml
|
|
<Target Name="PushToFoursatNuget" AfterTargets="Pack" Condition="'$(CI)' != 'true'">
|
|
<PropertyGroup>
|
|
<NugetPackagePath>$(PackageOutputPath)/$(PackageId).$(Version).nupkg</NugetPackagePath>
|
|
<PushCommand>dotnet nuget push "$(NugetPackagePath)" --source foursat-hosted --api-key admin:87zH26nbqT --skip-duplicate --configfile "$(MSBuildThisFileDirectory)../NuGet.config"</PushCommand>
|
|
</PropertyGroup>
|
|
<Exec Command="$(PushCommand)" WorkingDirectory="$(MSBuildThisFileDirectory)" />
|
|
</Target>
|
|
```
|
|
|
|
**استفاده**:
|
|
```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
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<configuration>
|
|
<packageSources>
|
|
<clear />
|
|
<add key="Nexus" value="http://194.5.195.53:32081/repository/nuget-all/index.json"
|
|
allowInsecureConnections="true" />
|
|
</packageSources>
|
|
<packageSourceCredentials>
|
|
<Nexus>
|
|
<add key="Username" value="admin" />
|
|
<add key="ClearTextPassword" value="87zH26nbqT" />
|
|
</Nexus>
|
|
</packageSourceCredentials>
|
|
</configuration>
|
|
```
|
|
|
|
**نکته**: 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<object>() // Cities property returns List<CityDto>
|
|
```
|
|
|
|
---
|
|
|
|
### 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<ConfigurationContract.ConfigurationContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<CommissionContract.CommissionContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
|
|
services.AddScoped(CreateAuthenticatedClient<UserAddressContract.UserAddressContractClient>);
|
|
```
|
|
|
|
---
|
|
|
|
### 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
|
|
# <Version>0.0.177</Version>
|
|
|
|
# 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`
|
|
|
|
**تغییرات:**
|
|
- ❌ حذف `<ActivationSection>` component (کارت فعالسازی/تمدید عضویت)
|
|
- ❌ حذف نمایش هزینه عضویت (`ActivationFee` و `MembershipGiftValue`)
|
|
|
|
**قبل:**
|
|
```razor
|
|
<!-- کارت فعالسازی -->
|
|
@if (!_membership?.IsActive ?? true)
|
|
{
|
|
<ActivationSection OnActivationSuccess="HandleActivationSuccess" />
|
|
}
|
|
|
|
<!-- نمایش هزینهها -->
|
|
<MudPaper>
|
|
<MudText>هزینه عضویت: @_clubConfig.ActivationFee.ToString("N0") ریال</MudText>
|
|
<MudText>هدیه عضویت: @_clubConfig.MembershipGiftValue.ToString("N0") ریال</MudText>
|
|
</MudPaper>
|
|
```
|
|
|
|
**بعد:**
|
|
```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<long> GetCurrentUserIdAsync()
|
|
{
|
|
return Task.FromResult(1L); // ❌ همیشه 1 برمیگرداند
|
|
}
|
|
|
|
// بعد - از UserAuthInfo میخواند
|
|
private Task<long> GetCurrentUserIdAsync()
|
|
{
|
|
return Task.FromResult(_authInfo.UserId); // ✅ UserId واقعی از session
|
|
}
|
|
```
|
|
|
|
##### تغییر 3: متد GetMyMembershipAsync
|
|
```csharp
|
|
// قبل
|
|
public async Task<ClubMembershipDto> 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<ClubMembershipDto> 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<ClubFeatureDto>();
|
|
}
|
|
|
|
// بعد - فراخوانی واقعی
|
|
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<GetClubConfigurationResponse> 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<GetClubFeaturesResponse> 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<GetClubMembershipResponse> 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<ClubMembershipDto?> 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<GetMyWeeklyBalancesResponse> GetMyWeeklyBalances(GetMyWeeklyBalancesRequest request, ServerCallContext context)
|
|
{
|
|
return await _dispatchRequestToCQRS.Handle<GetMyWeeklyBalancesRequest, GetMyWeeklyBalancesQuery, GetMyWeeklyBalancesResponse>(request, context);
|
|
}
|
|
```
|
|
|
|
**Query Handler:**
|
|
```csharp
|
|
public async Task<GetUserWeeklyBalancesResponseDto> 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
|