feat: Add monitoring alerts skeleton and enhance worker with notifications
This commit is contained in:
@@ -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 خالی پیدا نشد
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user