using Dapper; using FrameworkBLL.Archive; using GB5Shared.Connection; using GB5Shared.DTO.Framework.CommonConfig; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ServerConfig; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Npgsql; using Quartz; using System; using System.Collections.Generic; using System.Data; using System.Threading.Tasks; using static GB5Shared.GB5Constant.Constant; namespace FrameworkSL.Controllers.Archive { /// /// Nightly Quartz job that moves TEVENTLOG rows older than the configured retention /// boundary to TEVENTLOG_ARCHIVE, per tenant. /// /// Runs regardless of the schedulerEnabled flag — data retention is a critical path. /// /// Registration in Program.cs (add alongside OutboxPublishQuartzJob): /// /// services.AddQuartz(q => /// { /// var key = new JobKey("EventLogArchiveJob"); /// q.AddJob<EventLogArchiveQuartzJob>(o => o.WithIdentity(key)); /// q.AddTrigger(o => o /// .ForJob(key) /// .WithCronSchedule("0 0 2 * * ?")); // 2 AM UTC daily /// }); /// services.AddScoped<EventLogArchiveQuartzJob>(); /// /// [DisallowConcurrentExecution] public sealed class EventLogArchiveQuartzJob : IJob { private readonly IEventLogArchiveBLL _archiveBll; private readonly IApplicationConnection _appConnection; private readonly IOptionsSnapshot _systemDto; private readonly ILogger _logger; public EventLogArchiveQuartzJob( IEventLogArchiveBLL archiveBll, IApplicationConnection appConnection, IOptionsSnapshot systemDto, ILogger logger) { _archiveBll = archiveBll; _appConnection = appConnection; _systemDto = systemDto; _logger = logger; } public async Task Execute(IJobExecutionContext context) { _logger.LogInformation("EventLogArchiveJob: starting"); try { var tenants = await LoadTenantsAsync().ConfigureAwait(false); foreach (var tenant in tenants) { try { var login = new LoginDTO { ClientId = tenant.ClientId, DatabaseName = tenant.DatabaseName, DatabaseType = tenant.DbType, ConnectionDatabaseName = tenant.ConnectionName }; await _archiveBll.ArchiveForTenantAsync(login, context.CancellationToken) .ConfigureAwait(false); } catch (Exception ex) { // Log and continue — one tenant failure must not block others _logger.LogError(ex, "EventLogArchiveJob: failed for TenantId={TenantId}", tenant.ClientId); } } } catch (Exception ex) { _logger.LogError(ex, "EventLogArchiveJob: unexpected failure"); throw new JobExecutionException(ex, refireImmediately: false); } _logger.LogInformation("EventLogArchiveJob: done"); } // ── Helpers ─────────────────────────────────────────────────────────── private async Task> LoadTenantsAsync() { var systemConn = await _appConnection.Gb5SystemConnectionString().ConfigureAwait(false); int dbType = _systemDto.Value.DataBaseType; const string sql = @" SELECT SC.CLIENTID AS ClientId, SC.DATABASENAME AS DatabaseName, SC.DATABASETYPE AS DbType, SC.CONNECTIONNAME AS ConnectionName FROM MSERVERCONFIG SC JOIN MSERVER S ON SC.SERVERID = S.SERVERID WHERE SC.STATUS = 1 AND SC.CONNECTIONNAME <> 'ACTIVITI'"; using IDbConnection conn = dbType switch { DBTYPE.SQL => new SqlConnection(systemConn), DBTYPE.POSTGRESQL => new NpgsqlConnection(systemConn), _ => throw new NotSupportedException($"Unsupported DB type: {dbType}") }; return await conn.QueryAsync(sql).ConfigureAwait(false); } } }