feat: Add monitoring alerts skeleton and enhance worker with notifications

This commit is contained in:
masoodafar-web
2025-11-30 20:18:10 +03:30
parent 55fa71e09b
commit 199e7e99d1
23 changed files with 5038 additions and 1168 deletions

View File

@@ -0,0 +1,281 @@
# 🌳 Binary Tree Network Registration Guide
## 📋 Overview
از این پس، هر کاربر جدید که در سیستم ثبت می‌شود، **هم‌زمان** در دو ساختار قرار می‌گیرد:
1. **Old System**: `User.ParentId` (برای Backward Compatibility)
2. **New Binary Tree System**: `User.NetworkParentId` + `User.LegPosition` (Left/Right)
این تغییر تضمین می‌کند که:
- ✅ کاربران جدید بلافاصله در محاسبات Commission شرکت می‌کنند
- ✅ نیازی به Migration اضافی نیست
- ✅ Binary Tree Constraint رعایت می‌شود (حداکثر 2 فرزند)
---
## 🔧 Changes in Registration Flow
### قبل از تغییر:
```csharp
var entity = request.Adapt<User>();
entity.ReferralCode = UtilExtensions.Generate(digits: 10);
await _context.Users.AddAsync(entity, cancellationToken);
```
**مشکل**: فقط `ParentId` Set می‌شد، `NetworkParentId` و `LegPosition` خالی می‌ماند.
---
### بعد از تغییر:
```csharp
var entity = request.Adapt<User>();
entity.ReferralCode = UtilExtensions.Generate(digits: 10);
// === محاسبه موقعیت در Binary Tree ===
if (request.ParentId.HasValue)
{
var legPosition = await _networkPlacementService.CalculateLegPositionAsync(
request.ParentId.Value, cancellationToken);
if (legPosition.HasValue)
{
entity.NetworkParentId = request.ParentId.Value;
entity.LegPosition = legPosition.Value; // Left یا Right
}
else
{
// Parent پر است! Auto-Placement یا Error
var availableParent = await _networkPlacementService.FindAvailableParentAsync(
request.ParentId.Value, cancellationToken);
// ... Set کردن NetworkParentId و LegPosition با Parent جدید
}
}
await _context.Users.AddAsync(entity, cancellationToken);
```
**مزایا**:
-`NetworkParentId` و `LegPosition` به صورت خودکار محاسبه می‌شود
- ✅ Binary Tree Constraint چک می‌شود
- ✅ اگر Parent پر باشد، Auto-Placement انجام می‌شود
---
## 📐 Binary Tree Logic
### قوانین:
1. هر Parent فقط **2 فرزند** می‌تواند داشته باشد (Left & Right)
2. فرزند اول: `LegPosition = Left`
3. فرزند دوم: `LegPosition = Right`
4. اگر Parent پر باشد، سیستم به صورت BFS دنبال Parent خالی می‌گردد
### مثال:
```
User1 (Root)
/ \
User2 (L) User3 (R)
/ \
User4(L) User5(R)
```
- User2 → Parent=User1, Leg=Left
- User3 → Parent=User1, Leg=Right
- User4 → Parent=User2, Leg=Left
- User5 → Parent=User2, Leg=Right
اگر کاربر جدید با `ParentId=User1` بیاید:
- User1 پر است! (دو فرزند دارد)
- سیستم به User2 می‌رود (BFS)
- User2 هم پر است!
- به User3 می‌رود → User3 خالی است
- کاربر جدید → Parent=User3, Leg=Left
---
## 🛠️ NetworkPlacementService API
### 1. CalculateLegPositionAsync
محاسبه موقعیت (Left/Right) برای کاربر جدید زیر یک Parent مشخص.
```csharp
var legPosition = await _networkPlacementService.CalculateLegPositionAsync(parentId);
```
**Return Values**:
- `NetworkLeg.Left`: اگر Parent فرزند چپ ندارد
- `NetworkLeg.Right`: اگر Parent فرزند راست ندارد
- `null`: اگر Parent پر است (دو فرزند دارد)
---
### 2. CanAcceptChildAsync
بررسی اینکه آیا Parent می‌تواند فرزند جدید بپذیرد.
```csharp
bool canAccept = await _networkPlacementService.CanAcceptChildAsync(parentId);
```
**Return Values**:
- `true`: اگر Parent کمتر از 2 فرزند دارد
- `false`: اگر Parent پر است
---
### 3. FindAvailableParentAsync (Auto-Placement)
پیدا کردن اولین Parent خالی در Binary Tree با استفاده از BFS.
```csharp
long? availableParentId = await _networkPlacementService.FindAvailableParentAsync(rootParentId);
```
**Use Case**:
- زمانی که Parent مورد نظر پر است
- سیستم به صورت خودکار Parent جایگزین پیدا می‌کند
- از BFS استفاده می‌کند (Level-by-Level)
**Return Values**:
- `long`: شناسه Parent مناسب
- `null`: اگر هیچ Parent خالی پیدا نشد (تمام Binary Tree پر است!)
---
## ⚠️ Error Handling
### Scenario 1: Parent پر است و Auto-Placement موفق
```csharp
// Parent اصلی پر است
// سیستم Parent جدید پیدا می‌کند
_logger.LogWarning("Parent {ParentId} is full. Auto-placing under {NewParentId}");
```
**نتیجه**: کاربر با موفقیت در جای دیگری قرار می‌گیرد.
---
### Scenario 2: کل Binary Tree پر است
```csharp
throw new InvalidOperationException(
$"شبکه Parent با شناسه {parentId} پر است و نمی‌تواند کاربر جدید بپذیرد.");
```
**نتیجه**: Exception پرتاب می‌شود، ثبت کاربر انجام نمی‌شود.
**راه حل**:
- افزایش سطح Binary Tree
- یا تخصیص دستی Parent
---
### Scenario 3: Parent وجود ندارد
```csharp
var parentExists = await _context.Users.AnyAsync(u => u.Id == parentId);
if (!parentExists)
{
return null; // Parent نامعتبر
}
```
**نتیجه**: `null` برگردانده می‌شود، Exception پرتاب می‌شود.
---
## 📊 Logging & Monitoring
سیستم Log های زیر را می‌نویسد:
### Success:
```
User 123 placed in Binary Tree: Parent=45, Leg=Left
```
### Warning (Auto-Placement):
```
Parent 45 has no available leg! Finding alternative parent...
User 123 auto-placed under alternative Parent=67, Leg=Right
```
### Error (Binary Tree Full):
```
No available parent found in network for ParentId=45
```
---
## 🧪 Testing Scenarios
### Test 1: کاربر اول (Root)
```csharp
var command = new CreateNewUserCommand { Mobile = "09121234567" }; // No ParentId
// Result: ParentId=null, NetworkParentId=null, LegPosition=null
```
---
### Test 2: فرزند اول
```csharp
var command = new CreateNewUserCommand { Mobile = "09121234568", ParentId = 1 };
// Result: ParentId=1, NetworkParentId=1, LegPosition=Left
```
---
### Test 3: فرزند دوم
```csharp
var command = new CreateNewUserCommand { Mobile = "09121234569", ParentId = 1 };
// Result: ParentId=1, NetworkParentId=1, LegPosition=Right
```
---
### Test 4: فرزند سوم (Parent پر است)
```csharp
var command = new CreateNewUserCommand { Mobile = "09121234570", ParentId = 1 };
// Result: Auto-Placement → ParentId=1, NetworkParentId=2 (یا 3), LegPosition=Left
```
---
## 🔗 Related Files
- **Service Interface**: `CMSMicroservice.Application/Common/Interfaces/INetworkPlacementService.cs`
- **Service Implementation**: `CMSMicroservice.Infrastructure/Services/NetworkPlacementService.cs`
- **Handler**: `CMSMicroservice.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandHandler.cs`
- **DI Registration**: `CMSMicroservice.Infrastructure/ConfigureServices.cs` (خط 23)
---
## ✅ Checklist
- [x] `INetworkPlacementService` اضافه شد
- [x] `NetworkPlacementService` پیاده‌سازی شد
- [x] DI Container تنظیم شد
- [x] `CreateNewUserCommandHandler` اصلاح شد
- [ ] Unit Tests نوشته شود
- [ ] Integration Tests انجام شود
- [ ] Manual Testing با Postman/gRPC Client
---
## 🚀 Next Steps
1. **Test کردن**: ثبت چند کاربر با Parent مشابه و بررسی LegPosition
2. **Load Testing**: بررسی Performance با 10,000 کاربر
3. **Edge Cases**: تست Binary Tree Full scenario
4. **Documentation**: Update کردن API Docs
---
## 📞 Support
اگر مشکلی پیش آمد:
- Log های `NetworkPlacementService` را بررسی کنید
- چک کنید که DI به درستی تنظیم شده باشد
- از `CanAcceptChildAsync` برای Pre-Validation استفاده کنید

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
# 🔄 Migration Guide: ParentId → NetworkParentId
## 📋 Overview
در سیستم قدیمی، کاربران با استفاده از `User.ParentId` به هم متصل می‌شدند (Parent-Child relationship).
سیستم جدید **Network-Club-Commission** از یک **Binary Tree** استفاده می‌کند که نیاز به:
- `User.NetworkParentId` (شناسه پدر در شبکه باینری)
- `User.LegPosition` (Left یا Right)
برای اجرای صحیح Worker و محاسبات، **باید** تمام کاربران قدیمی Migrate شوند.
---
## ⚠️ Critical Issues
### مشکل 1: Binary Tree Constraint
- هر Parent فقط می‌تواند **2 فرزند** داشته باشد (Left & Right)
- اگر کاربری در سیستم قدیمی بیشتر از 2 فرزند دارد، Migration فقط **2 فرزند اول** را می‌گیرد
### مشکل 2: Orphaned Nodes
- اگر `ParentId` اشاره به یک کاربر نامعتبر (حذف شده) باشد، آن User **Orphaned** است
- Orphaned nodes در Binary Tree نادیده گرفته می‌شوند
---
## 🚀 Migration Methods
### روش 1: Automatic (Seeder - توصیه می‌شود)
Migration به صورت خودکار در `Program.cs` در حالت **Development** اجرا می‌شود:
```csharp
// در Program.cs
var migrationSeeder = new NetworkParentIdMigrationSeeder(dbContext, logger);
await migrationSeeder.SeedAsync();
```
**مزایا:**
- ✅ Idempotent (می‌توان چندین بار اجرا کرد، فقط یکبار تاثیر می‌گذارد)
- ✅ Validation اتوماتیک
- ✅ Logging کامل
**کجا اجرا می‌شود؟**
- فقط در **Development** environment
- هر بار که پروژه Run شود
---
### روش 2: Manual (Command)
اگر نیاز به اجرای دستی دارید:
```csharp
// درخواست از طریق MediatR
var result = await _mediator.Send(new MigrateNetworkParentIdCommand());
if (result.Success)
{
Console.WriteLine($"Migrated: {result.MigratedCount}");
Console.WriteLine($"Skipped: {result.SkippedCount}");
}
else
{
Console.WriteLine($"Error: {result.Message}");
}
```
---
### روش 3: SQL Script
برای Production یا اجرای مستقیم روی Database:
```bash
# فایل: CMSMicroservice.Infrastructure/Migrations/Scripts/20250601_MigrateParentIdToNetworkParentId.sql
```
**نکته مهم:**
قبل از اجرا، **حتماً** بررسی کنید که آیا کاربری بیش از 2 فرزند دارد:
```sql
SELECT
ParentId,
COUNT(*) as ChildCount,
STRING_AGG(CAST(Id AS VARCHAR), ', ') as ChildIds
FROM Users
WHERE ParentId IS NOT NULL
GROUP BY ParentId
HAVING COUNT(*) > 2;
```
---
## 📊 Validation After Migration
### 1. بررسی تعداد کاربران Migrate شده
```csharp
var stats = await _context.Users
.GroupBy(u => 1)
.Select(g => new
{
TotalUsers = g.Count(),
UsersWithNetworkParent = g.Count(u => u.NetworkParentId != null),
LeftChildren = g.Count(u => u.LegPosition == NetworkLeg.Left),
RightChildren = g.Count(u => u.LegPosition == NetworkLeg.Right)
})
.FirstOrDefaultAsync();
```
### 2. بررسی Orphaned Nodes
```sql
SELECT Id, NetworkParentId
FROM Users
WHERE NetworkParentId IS NOT NULL
AND NetworkParentId NOT IN (SELECT Id FROM Users);
```
### 3. بررسی Binary Tree Violation
```sql
SELECT NetworkParentId, COUNT(*) as ChildCount
FROM Users
WHERE NetworkParentId IS NOT NULL
GROUP BY NetworkParentId
HAVING COUNT(*) > 2;
```
---
## ⚙️ Algorithm Details
### مراحل Migration:
1. **Find Users**: یافتن کاربران با `ParentId != NULL` و `NetworkParentId == NULL`
2. **Group by Parent**: گروه‌بندی بر اساس ParentId
3. **Check Constraint**: اگر Parent بیش از 2 فرزند دارد، فقط 2 تا اول را بگیر
4. **Assign Values**:
```csharp
child.NetworkParentId = parentId;
child.LegPosition = (i == 0) ? NetworkLeg.Left : NetworkLeg.Right;
```
5. **Save & Validate**: ذخیره و اعتبارسنجی Binary Tree
---
## 🐛 Troubleshooting
### مشکل: Parent has more than 2 children
**راه حل:**
تصمیم دستی بگیرید که کدام 2 فرزند را نگه دارید:
```sql
-- بررسی کنید که کدام Parent مشکل دارد
SELECT ParentId, COUNT(*) as ChildCount
FROM Users
WHERE ParentId = 123
GROUP BY ParentId;
-- لیست فرزندان را ببینید
SELECT Id, FullName, CreatedAt
FROM Users
WHERE ParentId = 123
ORDER BY CreatedAt;
-- دستی NetworkParentId را برای 2 فرزند انتخابی Set کنید
UPDATE Users
SET NetworkParentId = 123, LegPosition = 0 -- Left
WHERE Id = 456;
UPDATE Users
SET NetworkParentId = 123, LegPosition = 1 -- Right
WHERE Id = 789;
```
---
### مشکل: Orphaned Nodes (Parent doesn't exist)
**راه حل:**
ParentId را NULL کنید یا به یک Parent معتبر متصل کنید:
```sql
-- گزینه 1: NULL کردن (Root شدن)
UPDATE Users
SET ParentId = NULL, NetworkParentId = NULL
WHERE ParentId = 999; -- 999 وجود ندارد
-- گزینه 2: اتصال به Parent دیگر
UPDATE Users
SET ParentId = 1, NetworkParentId = 1
WHERE ParentId = 999;
```
---
## ✅ Checklist Before Production
- [ ] Migration در Development اجرا شده؟
- [ ] Validation Errors بررسی شد؟
- [ ] Orphaned Nodes رفع شدند؟
- [ ] Binary Tree Violations رفع شدند؟
- [ ] Backup از Database گرفته شده؟
- [ ] Migration Script برای Production آماده است؟
- [ ] Testing کامل انجام شده؟
---
## 🔗 Related Files
- **Seeder**: `CMSMicroservice.Infrastructure/Data/Seeding/NetworkParentIdMigrationSeeder.cs`
- **Command**: `CMSMicroservice.Application/UserCQ/Commands/MigrateNetworkParentId/`
- **SQL Script**: `CMSMicroservice.Infrastructure/Migrations/Scripts/20250601_MigrateParentIdToNetworkParentId.sql`
- **Entity**: `CMSMicroservice.Domain/Entities/User.cs` (خطوط 16, 45, 49)
---
## 📞 Support
اگر مشکل خاصی با Migration پیدا کردید:
1. Log های Seeder را بررسی کنید
2. ValidationErrors را چک کنید
3. SQL Script را به صورت دستی اجرا کنید

View File

@@ -0,0 +1,732 @@
# 📊 Monitoring & Alerts System - Consolidated Implementation Report
**Date**: 2025-11-30
**Status**: ✅ Skeleton Implemented (30% Complete)
**Build**: ✅ Success
---
## 📋 Executive Summary
اسکلت کامل سیستم Monitoring & Alerts پیاده‌سازی شد. این سیستم شامل دو بخش اصلی است:
1. **Alert System**: اعلان‌های مدیریتی (Critical/Warning/Success) برای Admin
2. **User Notification System**: اعلان‌های کاربری (SMS/Email/Push) برای Users
فعلاً فقط Logging فعال است. Integration های اصلی (Sentry, Slack, SMS) آماده پیاده‌سازی هستند.
---
## 🏗️ Architecture Overview
```
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ IAlertService │ │ IUserNotificationService│ │
│ │ - Critical │ │ - Commission Received │ │
│ │ - Warning │ │ - Club Activation │ │
│ │ - Success │ │ - Payout Error │ │
│ └─────────────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↓ implements
┌─────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ AlertService │ │ UserNotificationService │ │
│ │ ✅ Logging │ │ ✅ Logging │ │
│ │ ⏳ Sentry │ │ ⏳ SMS Gateway │ │
│ │ ⏳ Slack │ │ ⏳ Email Service │ │
│ │ ⏳ Email │ │ ⏳ Push Notification │ │
│ └─────────────────────┘ └─────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ MonitoringSettings (Configuration) │ │
│ │ - SentryEnabled, SentryDsn │ │
│ │ - SlackEnabled, SlackWebhookUrl │ │
│ │ - EmailAlertsEnabled, AdminEmails │ │
│ │ - SmsNotificationsEnabled, SmsApiKey │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↓ used by
┌─────────────────────────────────────────────────────────┐
│ Background Workers / Handlers │
│ ┌──────────────────────────────────────────────────┐ │
│ │ WeeklyNetworkCommissionWorker │ │
│ │ - On Success: SendSuccessNotificationAsync() │ │
│ │ - On Error: SendCriticalAlertAsync() │ │
│ └──────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ ProcessUserPayoutsCommandHandler │ │
│ │ - On Payout: SendCommissionReceivedNotification│ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
---
## 📦 Implementation Details
### 1⃣ Alert Service (Admin Notifications)
**Interface**: `CMSMicroservice.Application/Common/Interfaces/IAlertService.cs`
```csharp
public interface IAlertService
{
Task SendCriticalAlertAsync(string title, string message, Exception? exception, CancellationToken ct);
Task SendWarningAlertAsync(string title, string message, CancellationToken ct);
Task SendSuccessNotificationAsync(string title, string message, CancellationToken ct);
}
```
**Implementation**: `CMSMicroservice.Infrastructure/Services/Monitoring/AlertService.cs`
**Current Behavior**:
```
🚨 CRITICAL ALERT: {Title} - {Message}
⚠️ WARNING ALERT: {Title} - {Message}
✅ SUCCESS: {Title} - {Message}
```
**Pending Integrations**:
- **Sentry**: Exception tracking & aggregation (TODO: `SentrySdk.CaptureException()`)
- **Slack**: Real-time alerts to channel (TODO: HTTP POST to webhook)
- **Email**: Alert emails to admin list (TODO: SMTP integration)
---
### 2⃣ User Notification Service
**Interface**: `CMSMicroservice.Application/Common/Interfaces/IAlertService.cs` (same file)
```csharp
public interface IUserNotificationService
{
Task SendCommissionReceivedNotificationAsync(long userId, decimal amount, int weekNumber, CancellationToken ct);
Task SendClubActivationNotificationAsync(long userId, CancellationToken ct);
Task SendPayoutErrorNotificationAsync(long userId, string errorMessage, CancellationToken ct);
}
```
**Implementation**: `CMSMicroservice.Infrastructure/Services/Monitoring/UserNotificationService.cs`
**Current Behavior**:
```
📧 Sending commission notification: User={UserId}, Amount={Amount}, Week={WeekNumber}
🎉 Sending club activation notification: User={UserId}
⚠️ Sending payout error notification: User={UserId}, Error={Error}
```
**Pending Integrations**:
- **SMS Gateway**: Kavenegar/Ghasedak integration (TODO: HTTP API call)
- **Email Service**: SMTP/SendGrid integration (TODO: template-based emails)
- **Push Notification**: FCM/OneSignal integration (TODO: mobile app notifications)
---
### 3⃣ Configuration Model
**File**: `CMSMicroservice.Infrastructure/Services/Monitoring/MonitoringSettings.cs`
```csharp
public class MonitoringSettings
{
public const string SectionName = "Monitoring";
// Sentry
public bool SentryEnabled { get; set; }
public string? SentryDsn { get; set; }
// Slack
public bool SlackEnabled { get; set; }
public string? SlackWebhookUrl { get; set; }
// Email Alerts (Admin)
public bool EmailAlertsEnabled { get; set; }
public List<string> AdminEmails { get; set; }
// SMS (User Notifications)
public bool SmsNotificationsEnabled { get; set; }
public string? SmsApiKey { get; set; }
public string? SmsGatewayUrl { get; set; }
}
```
**Config File**: `CMSMicroservice.WebApi/appsettings.json`
```json
{
"Monitoring": {
"SentryEnabled": false,
"SentryDsn": "",
"SlackEnabled": false,
"SlackWebhookUrl": "",
"EmailAlertsEnabled": false,
"AdminEmails": ["admin@example.com"],
"SmsNotificationsEnabled": false,
"SmsApiKey": "",
"SmsGatewayUrl": ""
}
}
```
---
### 4⃣ Dependency Injection
**File**: `CMSMicroservice.Infrastructure/ConfigureServices.cs`
```csharp
services.AddScoped<IAlertService, AlertService>();
services.AddScoped<IUserNotificationService, UserNotificationService>();
```
---
### 5⃣ Worker Integration
**File**: `CMSMicroservice.Infrastructure/BackgroundJobs/WeeklyNetworkCommissionWorker.cs`
**On Success**:
```csharp
await alertService.SendSuccessNotificationAsync(
"Weekly Commission Completed",
$"Week {previousWeekNumber}: {payoutsProcessed} payouts, {balancesToExpire.Count} balances expired");
```
**On Error**:
```csharp
await alertService.SendCriticalAlertAsync(
"Weekly Commission Worker Failed",
$"Worker execution {executionId} failed for week {GetPreviousWeekNumber()}",
ex,
cancellationToken);
```
---
## 🔌 Integration Roadmap
### Priority 1: Sentry (High - 1 hour)
**Why**: Critical error tracking & aggregation برای Production
**Steps**:
1. Install NuGet:
```bash
dotnet add package Sentry.AspNetCore
```
2. Configure in `Program.cs`:
```csharp
builder.WebHost.UseSentry(options =>
{
options.Dsn = builder.Configuration["Monitoring:SentryDsn"];
options.Environment = builder.Environment.EnvironmentName;
options.TracesSampleRate = 1.0;
});
```
3. Update `AlertService.SendCriticalAlertAsync()`:
```csharp
if (_settings.SentryEnabled && exception != null)
{
SentrySdk.CaptureException(exception, scope =>
{
scope.SetTag("alert.title", title);
scope.SetExtra("message", message);
});
}
```
4. Set DSN in `appsettings.Production.json`:
```json
{
"Monitoring": {
"SentryEnabled": true,
"SentryDsn": "https://xxxxx@sentry.io/12345"
}
}
```
---
### Priority 2: Slack Webhook (Medium - 2 hours)
**Why**: Real-time alerts به تیم Development/DevOps
**Steps**:
1. Create Incoming Webhook در Slack:
- Go to: `https://api.slack.com/apps`
- Create app → Incoming Webhooks → Add to channel
- Copy Webhook URL
2. Update `AlertService`:
```csharp
private readonly HttpClient _httpClient;
public async Task SendCriticalAlertAsync(...)
{
_logger.LogCritical(exception, "🚨 {Title} - {Message}", title, message);
if (_settings.SlackEnabled)
{
var payload = new
{
text = $"🚨 *{title}*",
attachments = new[]
{
new
{
color = "danger",
text = message,
fields = exception != null ? new[]
{
new { title = "Exception", value = exception.Message, @short = false }
} : null
}
}
};
await _httpClient.PostAsJsonAsync(_settings.SlackWebhookUrl, payload);
}
}
```
3. Set Webhook URL in config:
```json
{
"Monitoring": {
"SlackEnabled": true,
"SlackWebhookUrl": "https://hooks.slack.com/services/T00/B00/XXX"
}
}
```
---
### Priority 3: SMS Gateway - Kavenegar (Medium - 3 hours)
**Why**: اطلاع‌رسانی کمیسیون به کاربران
**Steps**:
1. Get API Key from Kavenegar:
- Sign up: `https://panel.kavenegar.com`
- API Key: Settings → API Key
2. Create `ISmsGatewayService`:
```csharp
public interface ISmsGatewayService
{
Task SendAsync(string mobile, string message, CancellationToken ct = default);
}
```
3. Implement `KavenegarSmsService`:
```csharp
public class KavenegarSmsService : ISmsGatewayService
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
public async Task SendAsync(string mobile, string message, CancellationToken ct)
{
var url = $"https://api.kavenegar.com/v1/{_apiKey}/sms/send.json";
var payload = new
{
receptor = mobile,
message = message
};
var response = await _httpClient.PostAsJsonAsync(url, payload, ct);
response.EnsureSuccessStatusCode();
}
}
```
4. Update `UserNotificationService.SendCommissionReceivedNotificationAsync()`:
```csharp
var user = await _context.Users.FindAsync(userId, ct);
if (user.SmsNotifications && _settings.SmsNotificationsEnabled)
{
var message = $"کمیسیون شما: {amount:N0} ریال برای هفته {weekNumber} واریز شد.";
await _smsGateway.SendAsync(user.Mobile, message, ct);
}
```
5. Configure:
```json
{
"Monitoring": {
"SmsNotificationsEnabled": true,
"SmsApiKey": "your-kavenegar-api-key"
}
}
```
---
### Priority 4: Email Alerts for Admins (Low - 2 hours)
**Why**: Backup notification channel
**Options**:
- **A) MailKit (SMTP)**:
```csharp
using var client = new SmtpClient();
await client.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("user@example.com", "password");
var message = new MimeMessage();
message.From.Add(new MailboxAddress("CMS Alerts", "noreply@foursat.ir"));
message.To.Add(new MailboxAddress("Admin", adminEmail));
message.Subject = $"[ALERT] {title}";
message.Body = new TextPart("html") { Text = htmlMessage };
await client.SendAsync(message);
```
- **B) SendGrid API**:
```csharp
var client = new SendGridClient(_settings.SendGridApiKey);
var msg = MailHelper.CreateSingleEmail(
from: new EmailAddress("noreply@foursat.ir", "CMS Alerts"),
to: new EmailAddress(adminEmail),
subject: $"[ALERT] {title}",
plainTextContent: message,
htmlContent: htmlMessage
);
await client.SendEmailAsync(msg);
```
**Config**:
```json
{
"Monitoring": {
"EmailAlertsEnabled": true,
"AdminEmails": ["admin@foursat.ir", "devops@foursat.ir"],
"SmtpServer": "smtp.gmail.com",
"SmtpPort": 587,
"SmtpUsername": "user@example.com",
"SmtpPassword": "password"
}
}
```
---
### Priority 5: Retry Logic با Exponential Backoff (Low - 1 hour)
**Why**: بهبود Reliability در صورت خطاهای Transient
**Implementation در Worker**:
```csharp
private async Task<T> RetryWithExponentialBackoffAsync<T>(
Func<Task<T>> operation,
int maxRetries = 3,
CancellationToken ct = default)
{
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
return await operation();
}
catch (Exception ex) when (attempt < maxRetries && IsTransientError(ex))
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt)); // 2^n: 1s, 2s, 4s
_logger.LogWarning(ex,
"Attempt {Attempt}/{MaxRetries} failed. Retrying in {Delay}s...",
attempt + 1, maxRetries, delay.TotalSeconds);
await Task.Delay(delay, ct);
}
}
throw new InvalidOperationException($"Operation failed after {maxRetries} retries");
}
private bool IsTransientError(Exception ex)
{
return ex is TimeoutException
|| ex is HttpRequestException
|| (ex is SqlException sqlEx && sqlEx.IsTransient);
}
```
**Usage**:
```csharp
// در ExecuteWeeklyCalculationAsync():
var balancesCalculated = await RetryWithExponentialBackoffAsync(async () =>
{
return await mediator.Send(new CalculateWeeklyBalancesCommand
{
WeekNumber = previousWeekNumber
}, cancellationToken);
}, maxRetries: 3, ct: cancellationToken);
```
---
## 🧪 Testing Guide
### Test 1: Alert Service (Console Logging)
```csharp
// در Controller یا Handler:
var alertService = _serviceProvider.GetRequiredService<IAlertService>();
await alertService.SendCriticalAlertAsync(
"Test Critical Alert",
"این یک تست برای Alert Service است",
new Exception("Sample exception"));
await alertService.SendSuccessNotificationAsync(
"Test Success",
"عملیات با موفقیت انجام شد");
```
**Expected Output**:
```
🚨 CRITICAL ALERT: Test Critical Alert - این یک تست برای Alert Service است
✅ SUCCESS: Test Success - عملیات با موفقیت انجام شد
```
---
### Test 2: User Notification Service
```csharp
var notificationService = _serviceProvider.GetRequiredService<IUserNotificationService>();
await notificationService.SendCommissionReceivedNotificationAsync(
userId: 123,
amount: 500_000,
weekNumber: 48);
```
**Expected Output**:
```
📧 Sending commission notification: User=123, Amount=500000, Week=48
```
---
### Test 3: Worker Integration
```bash
# Run Worker manually (for testing)
# تغییر زمان اجرا به 1 دقیقه بعد برای تست:
# در Worker: var delay = TimeSpan.FromMinutes(1);
dotnet run --project CMSMicroservice.WebApi
```
**Expected**:
- Worker starts
- After 1 minute → Executes calculation
- On success → Logs: `✅ SUCCESS: Weekly Commission Completed`
- On error → Logs: `🚨 CRITICAL ALERT: Weekly Commission Worker Failed`
---
### Test 4: Sentry Integration (بعد از پیاده‌سازی)
```csharp
// Throw یک exception برای تست:
throw new InvalidOperationException("Test Sentry integration");
```
**Check**: Sentry dashboard → Issues → باید exception جدید نمایش داده شود
---
### Test 5: Slack Integration (بعد از پیاده‌سازی)
```csharp
await alertService.SendCriticalAlertAsync("Test Slack", "Testing webhook integration", null);
```
**Check**: Slack channel → باید پیام جدید نمایش داده شود
---
### Test 6: SMS Integration (بعد از پیاده‌سازی)
```csharp
await notificationService.SendCommissionReceivedNotificationAsync(
userId: YOUR_USER_ID, // با شماره موبایل معتبر
amount: 100_000,
weekNumber: 48);
```
**Check**: موبایل کاربر → باید SMS دریافت شود
---
## 📊 Current Status & Progress
| Component | Status | Completion | Notes |
|-----------|--------|------------|-------|
| **Interfaces** | ✅ Done | 100% | `IAlertService`, `IUserNotificationService` |
| **Skeleton Implementations** | ✅ Done | 100% | Logging only |
| **Configuration Model** | ✅ Done | 100% | `MonitoringSettings` |
| **DI Registration** | ✅ Done | 100% | In `ConfigureServices.cs` |
| **Worker Integration** | ✅ Done | 100% | Success + Error alerts |
| **appsettings Structure** | ✅ Done | 100% | Monitoring section added |
| **Sentry Integration** | ⏳ Pending | 0% | Install package + configure DSN |
| **Slack Webhook** | ⏳ Pending | 0% | Create webhook + implement POST |
| **SMS Gateway** | ⏳ Pending | 0% | Choose provider + get API key |
| **Email Alerts** | ⏳ Pending | 0% | SMTP/SendGrid integration |
| **Retry Logic** | ⏳ Pending | 0% | Exponential backoff implementation |
| **Testing** | ⏳ Pending | 0% | Unit + Integration tests |
**Overall Progress**: 30% ✅ | 70% ⏳
---
## 📝 Important Notes
### 1. Production Readiness
- ⚠️ **فعلاً فقط Logging فعال است**
- ⚠️ برای Production **حداقل Sentry** باید فعال شود
- ⚠️ برای Critical systems حتماً Slack هم اضافه شود
### 2. User Preferences
- SMS/Email/Push باید بر اساس تنظیمات کاربر (`User.SmsNotifications`, etc.) ارسال شود
- در `UserNotificationService` باید ابتدا preferences چک شود
### 3. Rate Limiting
- برای SMS Gateway باید Rate Limiting در نظر گرفته شود
- پیشنهاد: استفاده از Queue (Hangfire/RabbitMQ) برای ارسال تعداد زیاد SMS
### 4. Cost Management
- SMS و Email هزینه دارند
- پیشنهاد: Batching برای ارسال گروهی
- پیشنهاد: Template-based messaging برای کاهش هزینه
### 5. Security
- API Keys در `appsettings.json` نباید commit شوند
- استفاده از Environment Variables یا Azure Key Vault
- مثال: `SmsApiKey: ${SMS_API_KEY}` در appsettings
### 6. Monitoring the Monitor
- خود Alert System هم باید Monitor شود
- اگر Slack/SMS fail شد، باید Fallback به Email یا Log باشد
- پیشنهاد: Dead Letter Queue برای failed notifications
---
## 🔗 File Reference Map
```
CMS/
├── src/
│ ├── CMSMicroservice.Application/
│ │ └── Common/
│ │ └── Interfaces/
│ │ └── IAlertService.cs ⭐
│ │
│ ├── CMSMicroservice.Infrastructure/
│ │ ├── Services/
│ │ │ └── Monitoring/
│ │ │ ├── AlertService.cs ⭐
│ │ │ ├── UserNotificationService.cs ⭐
│ │ │ └── MonitoringSettings.cs ⭐
│ │ │
│ │ ├── BackgroundJobs/
│ │ │ └── WeeklyNetworkCommissionWorker.cs ✏️ (Modified)
│ │ │
│ │ └── ConfigureServices.cs ✏️ (Modified)
│ │
│ └── CMSMicroservice.WebApi/
│ └── appsettings.json ✏️ (Modified)
└── docs/
└── monitoring-alerts-implementation-report.md 📄 (This file)
```
**Legend**:
- ⭐ = New file created
- ✏️ = Existing file modified
- 📄 = Documentation
---
## 🚀 Next Action Items
### Immediate (این هفته):
1. ✅ Review this document
2. ⏳ Decision: کدام Integration اول؟ (پیشنهاد: Sentry)
3. ⏳ Get credentials:
- Sentry DSN
- Slack Webhook URL
- SMS Gateway API Key
### Short-term (هفته آینده):
4. ⏳ Implement Sentry integration
5. ⏳ Implement Slack webhook
6. ⏳ Test in Staging environment
### Long-term (ماه آینده):
7. ⏳ Implement SMS Gateway (Kavenegar)
8. ⏳ Add Email alerts
9. ⏳ Implement Retry logic
10. ⏳ Write Unit/Integration tests
11. ⏳ Deploy to Production
---
## 📞 Contact & Support
**Implementation Questions**:
- Developer: GitHub Copilot (این گزارش)
- Review: Development Team
**Service Providers**:
- **Sentry**: https://sentry.io (Error tracking)
- **Slack**: https://api.slack.com/messaging/webhooks (Webhooks)
- **Kavenegar**: https://kavenegar.com (SMS Gateway - Iran)
- **Ghasedak**: https://ghasedak.me (SMS Gateway Alternative)
- **SendGrid**: https://sendgrid.com (Email service)
---
**Last Updated**: 2025-11-30
**Build Status**: ✅ Success
**Ready for**: Integration implementation
---
## 🎯 TL;DR (خلاصه برای رجوع سریع)
### چی ساخته شد:
- ✅ `IAlertService` + `AlertService` (Admin alerts)
- ✅ `IUserNotificationService` + `UserNotificationService` (User notifications)
- ✅ `MonitoringSettings` (Configuration model)
- ✅ Worker integration (Success/Error alerts)
- ✅ DI registration
- ✅ appsettings structure
### فعلاً چی کار می‌کنه:
- Logging به Console (🚨 Critical, ⚠️ Warning, ✅ Success)
### چی باید اضافه بشه:
1. **Sentry** - Error tracking (Priority: High)
2. **Slack** - Real-time alerts (Priority: Medium)
3. **SMS Gateway** - User notifications (Priority: Medium)
4. **Email** - Backup channel (Priority: Low)
5. **Retry Logic** - Reliability (Priority: Low)
### کجا باید نگاه کنی:
- Interfaces: `CMSMicroservice.Application/Common/Interfaces/IAlertService.cs`
- Implementations: `CMSMicroservice.Infrastructure/Services/Monitoring/`
- Worker: `CMSMicroservice.Infrastructure/BackgroundJobs/WeeklyNetworkCommissionWorker.cs`
- Config: `CMSMicroservice.WebApi/appsettings.json`
### چطوری تست کنی:
```csharp
await alertService.SendCriticalAlertAsync("Test", "Message", null);
// Output: 🚨 CRITICAL ALERT: Test - Message
```
### بعدش چیکار کنم:
1. Get Sentry DSN → Update appsettings.Production.json
2. Install `Sentry.AspNetCore` → Configure in Program.cs
3. Update `AlertService.SendCriticalAlertAsync()` → Add `SentrySdk.CaptureException()`
4. Test → Deploy

View File

@@ -0,0 +1,333 @@
# 📊 Monitoring & Alerts System - Implementation Report
**Date**: 2025-11-30
**Status**: ✅ Skeleton Implemented
**Completion**: 30% (Structure ready, integrations pending)
---
## 🎯 Overview
اسکلت سیستم **Monitoring & Alerts** برای پروژه CMS پیاده‌سازی شد. این سیستم به دو بخش اصلی تقسیم می‌شود:
1. **Alert System**: برای ارسال اعلان‌های مدیریتی (Critical Errors, Warnings, Success)
2. **User Notification System**: برای ارسال پیام به کاربران (کمیسیون، پرداخت، فعال‌سازی باشگاه)
---
## 📦 Files Created/Modified
### ✨ New Files:
1. **`IAlertService.cs`** (Interface)
- `SendCriticalAlertAsync()` - برای خطاهای Critical
- `SendWarningAlertAsync()` - برای Warning ها
- `SendSuccessNotificationAsync()` - برای موفقیت‌ها
2. **`IUserNotificationService.cs`** (Interface)
- `SendCommissionReceivedNotificationAsync()` - اعلان دریافت کمیسیون
- `SendClubActivationNotificationAsync()` - اعلان فعال‌سازی باشگاه
- `SendPayoutErrorNotificationAsync()` - اعلان خطا در پرداخت
3. **`AlertService.cs`** (Implementation - Skeleton)
- ✅ Logging به Console
- ⏳ TODO: Sentry Integration
- ⏳ TODO: Slack Integration
- ⏳ TODO: Email Integration
4. **`UserNotificationService.cs`** (Implementation - Skeleton)
- ✅ Logging به Console
- ⏳ TODO: SMS Gateway Integration
- ⏳ TODO: Email Service Integration
- ⏳ TODO: Push Notification Integration
5. **`MonitoringSettings.cs`** (Configuration Model)
- تنظیمات Sentry, Slack, Email, SMS
- قابل تنظیم از طریق `appsettings.json`
---
### ✏️ Modified Files:
1. **`ConfigureServices.cs`**
```csharp
services.AddScoped<IAlertService, AlertService>();
services.AddScoped<IUserNotificationService, UserNotificationService>();
```
2. **`WeeklyNetworkCommissionWorker.cs`**
- ✅ Integration با `IAlertService`
- ✅ ارسال Critical Alert در صورت خطا
- ✅ ارسال Success Notification پس از اتمام موفق
3. **`appsettings.json`**
- اضافه شدن بخش `Monitoring` با تنظیمات پیش‌فرض
---
## 🔧 Current Implementation
### Alert System Usage:
```csharp
// در Worker یا هر Handler دیگر:
try
{
// عملیات خطرناک
}
catch (Exception ex)
{
await _alertService.SendCriticalAlertAsync(
"Operation Failed",
"Description of what went wrong",
ex);
}
```
### Current Output:
```
🚨 CRITICAL ALERT: Weekly Commission Worker Failed - Worker execution abc-123 failed for week 2025-W48
```
---
## ⏳ Pending Integrations (TODO)
### 1. Sentry Integration
```csharp
// در AlertService.SendCriticalAlertAsync():
if (_settings.SentryEnabled)
{
SentrySdk.CaptureException(exception);
}
```
**Steps**:
- Install NuGet: `Sentry.AspNetCore`
- Configure DSN in `appsettings.json`
- Add to `Program.cs`: `builder.WebHost.UseSentry()`
---
### 2. Slack Integration
```csharp
// در AlertService:
if (_settings.SlackEnabled)
{
var payload = new
{
text = $"🚨 {title}",
attachments = new[]
{
new { text = message, color = "danger" }
}
};
await _httpClient.PostAsJsonAsync(_settings.SlackWebhookUrl, payload);
}
```
**Steps**:
- Create Slack Incoming Webhook
- Add URL to `appsettings.json`
- Install NuGet: `System.Net.Http.Json`
---
### 3. Email Alerts (برای Admin)
```csharp
// در AlertService:
if (_settings.EmailAlertsEnabled)
{
foreach (var email in _settings.AdminEmails)
{
await _emailService.SendAsync(
to: email,
subject: $"[ALERT] {title}",
body: message);
}
}
```
**Steps**:
- Configure SMTP settings
- Install NuGet: `MailKit` or use existing email service
- Add admin emails to config
---
### 4. SMS Notifications (برای کاربران)
```csharp
// در UserNotificationService.SendCommissionReceivedNotificationAsync():
var user = await _context.Users.FindAsync(userId);
if (user.SmsNotifications && _settings.SmsNotificationsEnabled)
{
var message = $"کمیسیون شما: {amount:N0} ریال برای هفته {weekNumber} واریز شد.";
await _smsGateway.SendAsync(user.Mobile, message);
}
```
**Steps**:
- Choose SMS provider (Kavenegar, Ghasedak, etc.)
- Get API Key
- Implement `ISmsGatewayService`
---
### 5. Retry Logic با Exponential Backoff
```csharp
// در Worker:
private async Task<T> RetryWithExponentialBackoff<T>(
Func<Task<T>> operation,
int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await operation();
}
catch (Exception ex) when (i < maxRetries - 1)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, i)); // 2^i seconds
_logger.LogWarning("Retry {Attempt}/{Max} after {Delay}s",
i + 1, maxRetries, delay.TotalSeconds);
await Task.Delay(delay);
}
}
}
```
---
## 📋 Configuration Example
در `appsettings.Production.json`:
```json
{
"Monitoring": {
"SentryEnabled": true,
"SentryDsn": "https://xxxxx@sentry.io/12345",
"SlackEnabled": true,
"SlackWebhookUrl": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX",
"EmailAlertsEnabled": true,
"AdminEmails": [
"admin@foursat.ir",
"devops@foursat.ir"
],
"SmsNotificationsEnabled": true,
"SmsApiKey": "your-kavenegar-api-key",
"SmsGatewayUrl": "https://api.kavenegar.com/v1/{apikey}/sms/send.json"
}
}
```
---
## 🧪 Testing
### Test 1: Alert Service
```csharp
var alertService = serviceProvider.GetRequiredService<IAlertService>();
await alertService.SendCriticalAlertAsync(
"Test Alert",
"This is a test critical alert");
```
**Expected**: Log در Console + (در Production) Sentry + Slack
---
### Test 2: User Notification
```csharp
var notificationService = serviceProvider.GetRequiredService<IUserNotificationService>();
await notificationService.SendCommissionReceivedNotificationAsync(
userId: 123,
amount: 500_000,
weekNumber: 48);
```
**Expected**: Log در Console + (در Production) SMS + Email
---
## 📊 Integration Priority
| Priority | Integration | Effort | Impact |
|----------|------------|--------|--------|
| 🔴 High | Sentry | 1 hour | Critical error tracking |
| 🟡 Medium | Slack | 2 hours | Real-time admin alerts |
| 🟡 Medium | SMS (Kavenegar) | 3 hours | User notifications |
| 🟢 Low | Email Alerts | 2 hours | Backup notification channel |
| 🟢 Low | Retry Logic | 1 hour | Reliability improvement |
---
## ✅ Current Status Summary
### Completed (30%):
- ✅ Interface definitions
- ✅ Skeleton implementations with Logging
- ✅ DI registration
- ✅ Worker integration
- ✅ Configuration model
- ✅ appsettings structure
### Pending (70%):
- ⏳ Sentry integration (5%)
- ⏳ Slack webhook (10%)
- ⏳ Email service (10%)
- ⏳ SMS gateway (15%)
- ⏳ Push notifications (10%)
- ⏳ Retry logic (5%)
- ⏳ Testing (10%)
- ⏳ Documentation (5%)
---
## 🚀 Next Steps
1. **Immediate** (در صورت نیاز):
- Enable Sentry for error tracking
- Setup Slack webhook for critical alerts
2. **Short-term** (هفته آینده):
- Integrate SMS gateway (Kavenegar)
- Test User notifications
3. **Long-term** (ماه آینده):
- Add Email service
- Implement Retry logic
- Push notification service
---
## 📝 Notes
- تمام TODO ها در کد با comment مشخص شده‌اند
- فعلاً فقط Logging فعال است
- برای Production باید حتماً یکی از Integration ها (Sentry/Slack) فعال شود
- SMS Gateway باید بر اساس پروژه انتخاب شود (Kavenegar, Ghasedak, etc.)
---
## 🔗 Related Files
- **Interfaces**: `CMSMicroservice.Application/Common/Interfaces/IAlertService.cs`
- **Implementations**: `CMSMicroservice.Infrastructure/Services/Monitoring/`
- **Worker**: `CMSMicroservice.Infrastructure/BackgroundJobs/WeeklyNetworkCommissionWorker.cs`
- **Config**: `CMSMicroservice.WebApi/appsettings.json`
---
**Report generated**: 2025-11-30
**Build Status**: ✅ Success
**Ready for**: Development continuation / Integration implementation