feat: implement Chatika account activation job for new club members
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
using CMSMicroservice.Application.Common.Interfaces;
|
||||
using CMSMicroservice.Domain.Entities.Club;
|
||||
using CMSMicroservice.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Polly;
|
||||
|
||||
namespace CMSMicroservice.Infrastructure.BackgroundJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Hangfire Job برای فعالسازی حساب چتیکا برای اعضای جدید باشگاه
|
||||
/// این Job کاربرانی که باشگاهشان فعال شده ولی حساب چتیکا ندارند را پیدا کرده و حساب میسازد
|
||||
/// </summary>
|
||||
public class ChatikaAccountActivationJob
|
||||
{
|
||||
private readonly IApplicationDbContext _context;
|
||||
private readonly IChatikaApiService _chatikaApiService;
|
||||
private readonly ILogger<ChatikaAccountActivationJob> _logger;
|
||||
private readonly ResiliencePipeline _retryPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// توضیحات فارسی فیچر چتیکا
|
||||
/// </summary>
|
||||
private const string ChatikaFeatureDescription =
|
||||
"🎉 تبریک! حساب هوش مصنوعی چتیکا شما فعال شد.\n\n" +
|
||||
"برای استفاده از امکانات رایگان چتیکا:\n" +
|
||||
"1️⃣ به وبسایت chatika.ir مراجعه کنید\n" +
|
||||
"2️⃣ شماره موبایل خود را وارد کنید\n" +
|
||||
"3️⃣ از دستیار هوشمند چتیکا لذت ببرید!\n\n" +
|
||||
"🔗 لینک ورود: https://chatika.ir";
|
||||
|
||||
public ChatikaAccountActivationJob(
|
||||
IApplicationDbContext context,
|
||||
IChatikaApiService chatikaApiService,
|
||||
ILogger<ChatikaAccountActivationJob> logger)
|
||||
{
|
||||
_context = context;
|
||||
_chatikaApiService = chatikaApiService;
|
||||
_logger = logger;
|
||||
|
||||
// Polly Retry: 3 تلاش با فاصله نمایی
|
||||
_retryPipeline = new ResiliencePipelineBuilder()
|
||||
.AddRetry(new Polly.Retry.RetryStrategyOptions
|
||||
{
|
||||
MaxRetryAttempts = 3,
|
||||
Delay = TimeSpan.FromSeconds(30),
|
||||
BackoffType = Polly.DelayBackoffType.Exponential,
|
||||
UseJitter = true,
|
||||
OnRetry = args =>
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"⚠️ Retry attempt {AttemptNumber} for Chatika API. Exception: {ExceptionType}",
|
||||
args.AttemptNumber,
|
||||
args.Outcome.Exception?.GetType().Name ?? "None");
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// اجرای Job برای فعالسازی حساب چتیکا
|
||||
/// </summary>
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var executionId = Guid.NewGuid();
|
||||
_logger.LogInformation(
|
||||
"🚀 [{ExecutionId}] Starting Chatika account activation job",
|
||||
executionId);
|
||||
|
||||
try
|
||||
{
|
||||
// پیدا کردن کاربرانی که:
|
||||
// 1. باشگاه فعال دارند (ClubMembership.IsActive = true)
|
||||
// 2. فیچر چتیکا (Id=1) رو دارند
|
||||
// 3. فیچر چتیکاشون هنوز Notes نداره (یعنی حساب ساخته نشده)
|
||||
var pendingUsers = await _context.UserClubFeatures
|
||||
.Include(ucf => ucf.User)
|
||||
.Include(ucf => ucf.ClubMembership)
|
||||
.Where(ucf =>
|
||||
ucf.ClubFeatureId == (long)ClubFeatureType.Chatika &&
|
||||
ucf.ClubMembership.IsActive &&
|
||||
!ucf.IsDeleted &&
|
||||
ucf.IsActive &&
|
||||
(ucf.Notes == null || ucf.Notes == "")) // حساب هنوز ساخته نشده
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (!pendingUsers.Any())
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"✅ [{ExecutionId}] No pending users for Chatika activation",
|
||||
executionId);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"📋 [{ExecutionId}] Found {Count} users pending Chatika activation",
|
||||
executionId,
|
||||
pendingUsers.Count);
|
||||
|
||||
var successCount = 0;
|
||||
var failCount = 0;
|
||||
|
||||
foreach (var userFeature in pendingUsers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// بررسی تکراری نبودن (Double-check)
|
||||
if (!string.IsNullOrEmpty(userFeature.Notes))
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"⏭️ Skipping user {UserId} - already processed",
|
||||
userFeature.UserId);
|
||||
continue;
|
||||
}
|
||||
|
||||
var user = userFeature.User;
|
||||
var fullName = $"{user.FirstName} {user.LastName}".Trim();
|
||||
if (string.IsNullOrWhiteSpace(fullName)) fullName = user.Mobile;
|
||||
|
||||
// کال کردن API چتیکا با retry
|
||||
var result = await _retryPipeline.ExecuteAsync(
|
||||
async ct => await _chatikaApiService.CreateAccountAsync(
|
||||
user.Mobile,
|
||||
fullName,
|
||||
ct),
|
||||
cancellationToken);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
// آپدیت فیچر با توضیحات و URL
|
||||
var description = ChatikaFeatureDescription;
|
||||
if (!string.IsNullOrEmpty(result.AccessUrl))
|
||||
{
|
||||
description = description.Replace(
|
||||
"https://chatika.ir",
|
||||
result.AccessUrl);
|
||||
}
|
||||
|
||||
userFeature.Notes = description;
|
||||
userFeature.IsActive = true;
|
||||
userFeature.LastModified = DateTime.Now;
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"✅ Chatika account activated for user {UserId}",
|
||||
userFeature.UserId);
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"⚠️ Failed to create Chatika account for user {UserId}: {Error}",
|
||||
userFeature.UserId,
|
||||
result.ErrorMessage);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"❌ Error processing Chatika activation for user {UserId}",
|
||||
userFeature.UserId);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"🏁 [{ExecutionId}] Chatika activation job completed. Success: {Success}, Failed: {Failed}",
|
||||
executionId,
|
||||
successCount,
|
||||
failCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"❌ [{ExecutionId}] Chatika activation job failed",
|
||||
executionId);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user