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,171 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Enums;
using CMSMicroservice.Infrastructure.Persistence;
namespace CMSMicroservice.Infrastructure.Data.Seeding;
/// <summary>
/// Seeder for migrating existing User.ParentId to User.NetworkParentId
/// این Seeder فقط یک بار اجرا می‌شود و داده‌های قدیمی را به ساختار Binary Tree جدید منتقل می‌کند
/// </summary>
public class NetworkParentIdMigrationSeeder
{
private readonly ApplicationDbContext _context;
private readonly ILogger<NetworkParentIdMigrationSeeder> _logger;
public NetworkParentIdMigrationSeeder(
ApplicationDbContext context,
ILogger<NetworkParentIdMigrationSeeder> logger)
{
_context = context;
_logger = logger;
}
public async Task SeedAsync(CancellationToken cancellationToken = default)
{
_logger.LogInformation("=== Starting ParentId → NetworkParentId Migration ===");
// Step 1: Validation - Check if migration already done
var alreadyMigrated = await _context.Users
.Where(u => u.ParentId != null && u.NetworkParentId != null)
.AnyAsync(cancellationToken);
if (alreadyMigrated)
{
_logger.LogWarning("⚠️ Migration already completed! Skipping...");
return;
}
// Step 2: Find users with ParentId but no NetworkParentId
var usersToMigrate = await _context.Users
.Where(u => u.ParentId != null && u.NetworkParentId == null)
.OrderBy(u => u.Id)
.ToListAsync(cancellationToken);
if (usersToMigrate.Count == 0)
{
_logger.LogInformation("✅ No users to migrate. All done!");
return;
}
_logger.LogInformation($"📊 Found {usersToMigrate.Count} users to migrate");
// Step 3: Group by ParentId to check binary tree constraint
var parentGroups = usersToMigrate.GroupBy(u => u.ParentId);
int migratedCount = 0;
int skippedCount = 0;
foreach (var group in parentGroups)
{
var parentId = group.Key;
var children = group.OrderBy(u => u.Id).ToList(); // ترتیب بر اساس Id
if (children.Count > 2)
{
_logger.LogWarning(
"⚠️ Parent {ParentId} has {Count} children! Binary tree allows max 2. Taking first 2...",
parentId, children.Count);
children = children.Take(2).ToList();
skippedCount += (group.Count() - 2);
}
// Assign NetworkParentId and LegPosition
for (int i = 0; i < children.Count && i < 2; i++)
{
var child = children[i];
child.NetworkParentId = parentId;
child.LegPosition = i == 0 ? NetworkLeg.Left : NetworkLeg.Right;
_logger.LogDebug(
"✅ Migrated User {UserId}: Parent={ParentId}, Leg={Leg}",
child.Id, parentId, child.LegPosition);
migratedCount++;
}
}
// Step 4: Save changes
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"✅ Migration Completed! Migrated={Migrated}, Skipped={Skipped}",
migratedCount, skippedCount);
// Step 5: Post-Migration Validation
await ValidateMigrationAsync(cancellationToken);
}
private async Task ValidateMigrationAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("🔍 Validating Migration...");
// Check 1: Orphaned nodes (NetworkParent doesn't exist)
var orphanedUsers = await _context.Users
.Where(u => u.NetworkParentId != null &&
!_context.Users.Any(p => p.Id == u.NetworkParentId))
.Select(u => new { u.Id, u.NetworkParentId })
.ToListAsync(cancellationToken);
if (orphanedUsers.Any())
{
_logger.LogError(
"❌ Found {Count} orphaned users (NetworkParent doesn't exist): {Ids}",
orphanedUsers.Count,
string.Join(", ", orphanedUsers.Select(u => u.Id)));
}
// Check 2: Binary tree violation (more than 2 children per parent)
var parentsWithTooManyChildren = await _context.Users
.Where(u => u.NetworkParentId != null)
.GroupBy(u => u.NetworkParentId)
.Select(g => new { ParentId = g.Key, Count = g.Count() })
.Where(x => x.Count > 2)
.ToListAsync(cancellationToken);
if (parentsWithTooManyChildren.Any())
{
_logger.LogError(
"❌ Binary tree violation! {Count} parents have more than 2 children",
parentsWithTooManyChildren.Count);
foreach (var parent in parentsWithTooManyChildren)
{
_logger.LogError(" Parent {ParentId} has {Count} children", parent.ParentId, parent.Count);
}
}
// Check 3: Statistics
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(cancellationToken);
if (stats != null)
{
_logger.LogInformation("📊 Migration Statistics:");
_logger.LogInformation(" Total Users: {Total}", stats.TotalUsers);
_logger.LogInformation(" Users with NetworkParent: {Count}", stats.UsersWithNetworkParent);
_logger.LogInformation(" Left Children: {Count}", stats.LeftChildren);
_logger.LogInformation(" Right Children: {Count}", stats.RightChildren);
}
if (!orphanedUsers.Any() && !parentsWithTooManyChildren.Any())
{
_logger.LogInformation("✅ Validation Passed! Binary tree is intact.");
}
else
{
_logger.LogError("❌ Validation Failed! Please fix issues manually.");
}
}
}