using System; using System.Collections.Generic; using System.Data; using System.Threading; using System.Threading.Tasks; using Dapper; using FrameworkDAL.DTO.SessionStore; using FrameworkDAL.Query.SessionStore; using GB5Shared.Connection; using GB5Shared.DTO.Framework.CommonConfig; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Npgsql; using static GB5Shared.GB5Constant.Constant; namespace FrameworkDAL.CustomCode.SessionStore { /// /// Performs all MSESSIONSTORE operations against the GB5 system database. /// Does NOT use IQueryExecutor (which routes to tenant DBs). /// Uses a direct Dapper connection to the system DB, matching the pattern /// used by EventLogSubBLL for system-level operations. /// public class SessionStoreDAL : ISessionStoreDAL { private readonly IApplicationConnection _appConnection; private readonly IOptionsMonitor _systemDto; private readonly ILogger _logger; public SessionStoreDAL( IApplicationConnection appConnection, IOptionsMonitor systemDto, ILogger logger) { _appConnection = appConnection; _systemDto = systemDto; _logger = logger; } // ── Helpers ───────────────────────────────────────────────────────── private async Task OpenSystemConnectionAsync(CancellationToken ct) { string connStr = await _appConnection.Gb5SystemConnectionString().ConfigureAwait(false); int dbType = _systemDto.CurrentValue.DataBaseType; IDbConnection conn = dbType switch { DBTYPE.SQL => new SqlConnection(connStr), DBTYPE.POSTGRESQL => new NpgsqlConnection(connStr), _ => throw new NotSupportedException($"Unsupported GB5 system DB type: {dbType}") }; conn.Open(); return conn; } private string UpsertSql { get { int dbType = _systemDto.CurrentValue.DataBaseType; return dbType == DBTYPE.POSTGRESQL ? SessionStoreQB.UPSERT_SESSION_POSTGRESQL : SessionStoreQB.UPSERT_SESSION_SQL; } } private string DeleteOldSql { get { int dbType = _systemDto.CurrentValue.DataBaseType; return dbType == DBTYPE.POSTGRESQL ? SessionStoreQB.DELETE_OLD_SESSIONS_POSTGRESQL : SessionStoreQB.DELETE_OLD_SESSIONS_SQL; } } private string GetUnusedSql { get { int dbType = _systemDto.CurrentValue.DataBaseType; return dbType == DBTYPE.POSTGRESQL ? SessionStoreQB.GET_UNUSED_SESSIONS_POSTGRESQL : SessionStoreQB.GET_UNUSED_SESSIONS_SQL; } } // ── Operations ─────────────────────────────────────────────────────── public async Task UpsertSessionAsync(SessionStoreDTO dto, CancellationToken ct = default) { using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); await conn.ExecuteAsync( new CommandDefinition( UpsertSql, new { dto.ServerconfigId, dto.LoginEventLogId, dto.LoginTime, dto.LastLoginUsedTime, dto.MachineIp, dto.UserId, dto.UserCode, dto.UserName }, cancellationToken: ct)).ConfigureAwait(false); _logger.LogInformation( "SessionStore upserted | ServerconfigId={ServerconfigId} LoginEventLogId={LoginEventLogId} UserId={UserId}", dto.ServerconfigId, dto.LoginEventLogId, dto.UserId); } public async Task UpdateSessionAsync(int serverConfigId, int loginEventLogId, DateTime lastUsedTime, CancellationToken ct = default) { int count = await GetSessionCountAsync(serverConfigId, loginEventLogId, ct).ConfigureAwait(false); if (count == 0) { _logger.LogWarning( "SessionStore update skipped — session not found | ServerconfigId={ServerconfigId} LoginEventLogId={LoginEventLogId}", serverConfigId, loginEventLogId); return; } using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); await conn.ExecuteAsync( new CommandDefinition( SessionStoreQB.UPDATE_SESSION, new { LastLoginUsedTime = lastUsedTime, ServerconfigId = serverConfigId, LoginEventLogId = loginEventLogId }, cancellationToken: ct)).ConfigureAwait(false); } public async Task DeleteSessionAsync(int serverConfigId, int loginEventLogId, CancellationToken ct = default) { using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); await conn.ExecuteAsync( new CommandDefinition( SessionStoreQB.DELETE_SESSION, new { ServerconfigId = serverConfigId, LoginEventLogId = loginEventLogId }, cancellationToken: ct)).ConfigureAwait(false); _logger.LogInformation( "SessionStore deleted | ServerconfigId={ServerconfigId} LoginEventLogId={LoginEventLogId}", serverConfigId, loginEventLogId); } public async Task GetSessionCountAsync(int serverConfigId, int loginEventLogId, CancellationToken ct = default) { using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); return await conn.ExecuteScalarAsync( new CommandDefinition( SessionStoreQB.SELECT_SESSION_COUNT, new { ServerconfigId = serverConfigId, LoginEventLogId = loginEventLogId }, cancellationToken: ct)).ConfigureAwait(false); } public async Task> GetUnusedSessionsAsync(int serverConfigId, int expiryMinutes, CancellationToken ct = default) { using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); return await conn.QueryAsync( new CommandDefinition( GetUnusedSql, new { ServerconfigId = serverConfigId, ExpiryTime = expiryMinutes }, cancellationToken: ct)).ConfigureAwait(false); } public async Task DeleteOldSessionsAsync(DateTime todayDate, CancellationToken ct = default) { using IDbConnection conn = await OpenSystemConnectionAsync(ct).ConfigureAwait(false); int rows = await conn.ExecuteAsync( new CommandDefinition( DeleteOldSql, new { TodayDate = todayDate }, cancellationToken: ct)).ConfigureAwait(false); _logger.LogInformation("SessionStore cleanup removed {RowCount} stale session(s)", rows); } } }