feat: add MagicWalletCycleSeedService — one-time BackgroundService to seed ClubMembershipCycle for existing active memberships, controlled via appsettings SeedWorkers:MagicWalletCycleSeed:Enabled
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.Club;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// سرویس یکباره برای Seed کردن ClubMembershipCycle برای عضویتهای فعال موجود.
|
||||
/// عضویتهایی که قبل از اضافه شدن سیستم Cycle ایجاد شدهاند رکورد ندارند.
|
||||
/// این Worker در استارتاپ اجرا شده، برای آنها CycleNumber=1 میسازد و سپس متوقف میشود.
|
||||
///
|
||||
/// فعال/غیرفعال از appsettings:
|
||||
/// "SeedWorkers": { "MagicWalletCycleSeed": { "Enabled": true } }
|
||||
///
|
||||
/// بعد از اجرای موفق، Enabled را false کنید.
|
||||
/// </summary>
|
||||
public class MagicWalletCycleSeedService : BackgroundService
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<MagicWalletCycleSeedService> _logger;
|
||||
|
||||
public MagicWalletCycleSeedService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<MagicWalletCycleSeedService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
// صبر کوتاه برای اطمینان از آماده شدن دیتابیس
|
||||
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
|
||||
|
||||
_logger.LogInformation("MagicWalletCycleSeedService started — scanning active memberships without cycles...");
|
||||
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<IApplicationDbContext>();
|
||||
|
||||
var seededCount = await SeedMissingCycles(context, stoppingToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"MagicWalletCycleSeedService completed — seeded {Count} ClubMembershipCycle records",
|
||||
seededCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "MagicWalletCycleSeedService encountered an error");
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"MagicWalletCycleSeedService finished — shutting down (one-time execution). " +
|
||||
"Set SeedWorkers:MagicWalletCycleSeed:Enabled to false in appsettings.");
|
||||
}
|
||||
|
||||
private async Task<int> SeedMissingCycles(IApplicationDbContext context, CancellationToken ct)
|
||||
{
|
||||
// پیدا کردن عضویتهای فعالی که هنوز Cycle ندارند
|
||||
var membershipIdsWithCycle = await context.ClubMembershipCycles
|
||||
.Where(c => !c.IsDeleted)
|
||||
.Select(c => c.ClubMembershipId)
|
||||
.Distinct()
|
||||
.ToListAsync(ct);
|
||||
|
||||
var membershipsWithoutCycle = await context.ClubMemberships
|
||||
.Where(cm => !cm.IsDeleted && cm.IsActive && !membershipIdsWithCycle.Contains(cm.Id))
|
||||
.Select(cm => new
|
||||
{
|
||||
cm.Id,
|
||||
cm.UserId,
|
||||
cm.ActivatedAt
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
if (membershipsWithoutCycle.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No active memberships without cycles found — nothing to seed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Found {Count} active memberships without ClubMembershipCycle — seeding...",
|
||||
membershipsWithoutCycle.Count);
|
||||
|
||||
var seeded = 0;
|
||||
foreach (var cm in membershipsWithoutCycle)
|
||||
{
|
||||
if (ct.IsCancellationRequested) break;
|
||||
|
||||
try
|
||||
{
|
||||
var cycle = new ClubMembershipCycle
|
||||
{
|
||||
UserId = cm.UserId,
|
||||
ClubMembershipId = cm.Id,
|
||||
CycleNumber = 1,
|
||||
PackagePurchasedAt = cm.ActivatedAt ?? DateTime.UtcNow,
|
||||
IsCurrentCycle = true,
|
||||
PurchaseMethod = 0,
|
||||
PackageAmount = 0
|
||||
};
|
||||
|
||||
context.ClubMembershipCycles.Add(cycle);
|
||||
seeded++;
|
||||
|
||||
_logger.LogDebug(
|
||||
"Seeded ClubMembershipCycle for UserId={UserId}, MembershipId={MembershipId}, ActivatedAt={ActivatedAt}",
|
||||
cm.UserId, cm.Id, cm.ActivatedAt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex,
|
||||
"Failed to seed cycle for MembershipId={MembershipId}, UserId={UserId}",
|
||||
cm.Id, cm.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
return seeded;
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,10 @@ public static class ConfigureServices
|
||||
// One-time: Initialize inventory records for existing products
|
||||
services.AddHostedService<InventoryInitializerService>();
|
||||
|
||||
// One-time: Seed ClubMembershipCycle for existing active memberships
|
||||
if (configuration.GetValue<bool>("SeedWorkers:MagicWalletCycleSeed:Enabled"))
|
||||
services.AddHostedService<MagicWalletCycleSeedService>();
|
||||
|
||||
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
|
||||
Reference in New Issue
Block a user