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
@@ -0,0 +1,60 @@
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Infrastructure.Services.Monitoring;
/// <summary>
/// پیاده‌سازی اولیه AlertService
/// TODO: Integration با Sentry, Slack, Email
/// </summary>
public class AlertService : IAlertService
{
private readonly ILogger<AlertService> _logger;
public AlertService(ILogger<AlertService> logger)
{
_logger = logger;
}
public async Task SendCriticalAlertAsync(
string title,
string message,
Exception? exception = null,
CancellationToken cancellationToken = default)
{
_logger.LogCritical(exception, "🚨 CRITICAL ALERT: {Title} - {Message}", title, message);
// TODO: Integration
// - Send to Sentry
// - Send to Slack
// - Send Email to Admins
await Task.CompletedTask;
}
public async Task SendWarningAlertAsync(
string title,
string message,
CancellationToken cancellationToken = default)
{
_logger.LogWarning("⚠️ WARNING ALERT: {Title} - {Message}", title, message);
// TODO: Integration
// - Send to Slack
// - Log to monitoring system
await Task.CompletedTask;
}
public async Task SendSuccessNotificationAsync(
string title,
string message,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("✅ SUCCESS: {Title} - {Message}", title, message);
// TODO: Optional Slack notification for important success events
await Task.CompletedTask;
}
}
@@ -0,0 +1,57 @@
using System.Collections.Generic;
namespace CMSMicroservice.Infrastructure.Services.Monitoring;
/// <summary>
/// تنظیمات Monitoring و Alerting
/// در appsettings.json تعریف می‌شود
/// </summary>
public class MonitoringSettings
{
public const string SectionName = "Monitoring";
/// <summary>
/// فعال بودن Sentry
/// </summary>
public bool SentryEnabled { get; set; } = false;
/// <summary>
/// Sentry DSN
/// </summary>
public string? SentryDsn { get; set; }
/// <summary>
/// فعال بودن Slack Notifications
/// </summary>
public bool SlackEnabled { get; set; } = false;
/// <summary>
/// Slack Webhook URL
/// </summary>
public string? SlackWebhookUrl { get; set; }
/// <summary>
/// فعال بودن Email Alerts
/// </summary>
public bool EmailAlertsEnabled { get; set; } = false;
/// <summary>
/// لیست ایمیل‌های Admin برای دریافت Alert
/// </summary>
public List<string> AdminEmails { get; set; } = new();
/// <summary>
/// فعال بودن SMS Notifications به کاربران
/// </summary>
public bool SmsNotificationsEnabled { get; set; } = false;
/// <summary>
/// SMS Gateway API Key
/// </summary>
public string? SmsApiKey { get; set; }
/// <summary>
/// SMS Gateway Base URL
/// </summary>
public string? SmsGatewayUrl { get; set; }
}
@@ -0,0 +1,69 @@
using CMSMicroservice.Application.Common.Interfaces;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Infrastructure.Services.Monitoring;
/// <summary>
/// پیاده‌سازی اولیه UserNotificationService
/// TODO: Integration با SMS Gateway, Email Service, Push Notification
/// </summary>
public class UserNotificationService : IUserNotificationService
{
private readonly IApplicationDbContext _context;
private readonly ILogger<UserNotificationService> _logger;
public UserNotificationService(
IApplicationDbContext context,
ILogger<UserNotificationService> logger)
{
_context = context;
_logger = logger;
}
public async Task SendCommissionReceivedNotificationAsync(
long userId,
decimal amount,
int weekNumber,
CancellationToken cancellationToken = default)
{
_logger.LogInformation(
"📧 Sending commission notification: User={UserId}, Amount={Amount}, Week={WeekNumber}",
userId, amount, weekNumber);
// TODO: Implementation
// 1. Get User preferences (SMS/Email/Push enabled?)
// 2. Send SMS via SMS Gateway
// 3. Send Email via Email Service
// 4. Send Push Notification
await Task.CompletedTask;
}
public async Task SendClubActivationNotificationAsync(
long userId,
CancellationToken cancellationToken = default)
{
_logger.LogInformation("🎉 Sending club activation notification: User={UserId}", userId);
// TODO: Implementation
// - Welcome message for club membership
await Task.CompletedTask;
}
public async Task SendPayoutErrorNotificationAsync(
long userId,
string errorMessage,
CancellationToken cancellationToken = default)
{
_logger.LogWarning(
"⚠️ Sending payout error notification: User={UserId}, Error={Error}",
userId, errorMessage);
// TODO: Implementation
// - Notify user about payment failure
// - Provide retry instructions
await Task.CompletedTask;
}
}
@@ -0,0 +1,116 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using System.Collections.Generic;
namespace CMSMicroservice.Infrastructure.Services;
/// <summary>
/// پیاده‌سازی سرویس محاسبه موقعیت در Binary Tree
/// </summary>
public class NetworkPlacementService : INetworkPlacementService
{
private readonly IApplicationDbContext _context;
private readonly ILogger<NetworkPlacementService> _logger;
public NetworkPlacementService(
IApplicationDbContext context,
ILogger<NetworkPlacementService> logger)
{
_context = context;
_logger = logger;
}
public async Task<NetworkLeg?> CalculateLegPositionAsync(long parentId, CancellationToken cancellationToken = default)
{
// بررسی وجود Parent
var parentExists = await _context.Users.AnyAsync(u => u.Id == parentId, cancellationToken);
if (!parentExists)
{
_logger.LogWarning("Parent {ParentId} does not exist", parentId);
return null;
}
// شمارش فرزندان فعلی
var children = await _context.Users
.Where(u => u.NetworkParentId == parentId)
.Select(u => new { u.LegPosition })
.ToListAsync(cancellationToken);
if (children.Count >= 2)
{
_logger.LogWarning("Parent {ParentId} already has 2 children. Binary Tree is full!", parentId);
return null; // Binary Tree پر است
}
// بررسی کدام Leg خالی است
var hasLeft = children.Any(c => c.LegPosition == NetworkLeg.Left);
var hasRight = children.Any(c => c.LegPosition == NetworkLeg.Right);
if (!hasLeft)
{
_logger.LogDebug("Parent {ParentId}: Left leg is available", parentId);
return NetworkLeg.Left;
}
if (!hasRight)
{
_logger.LogDebug("Parent {ParentId}: Right leg is available", parentId);
return NetworkLeg.Right;
}
// نباید به اینجا برسیم (چون Count < 2 بود)
_logger.LogError("Unexpected state: Parent {ParentId} has {Count} children but no available leg",
parentId, children.Count);
return null;
}
public async Task<bool> CanAcceptChildAsync(long parentId, CancellationToken cancellationToken = default)
{
var childCount = await _context.Users
.CountAsync(u => u.NetworkParentId == parentId, cancellationToken);
return childCount < 2;
}
public async Task<long?> FindAvailableParentAsync(long rootParentId, CancellationToken cancellationToken = default)
{
// BFS (Breadth-First Search) برای پیدا کردن اولین Parent با جای خالی
var queue = new Queue<long>();
queue.Enqueue(rootParentId);
var visited = new HashSet<long>();
while (queue.Count > 0)
{
var currentParentId = queue.Dequeue();
if (visited.Contains(currentParentId))
continue;
visited.Add(currentParentId);
// بررسی کنید که آیا این Parent می‌تواند فرزند بپذیرد
var canAccept = await CanAcceptChildAsync(currentParentId, cancellationToken);
if (canAccept)
{
_logger.LogInformation("Found available parent: {ParentId}", currentParentId);
return currentParentId;
}
// اضافه کردن فرزندان به صف برای جستجو
var children = await _context.Users
.Where(u => u.NetworkParentId == currentParentId)
.Select(u => u.Id)
.ToListAsync(cancellationToken);
foreach (var childId in children)
{
queue.Enqueue(childId);
}
}
_logger.LogWarning("No available parent found in network starting from {RootParentId}", rootParentId);
return null; // هیچ Parent خالی پیدا نشد
}
}