using System.Data.Common; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; namespace DXPDAL.Auth; public class AuthDAL : IAuthDAL { private readonly IQueryExecutor _QueryExecutor; public AuthDAL(IQueryExecutor queryExecutor) { _QueryExecutor = queryExecutor; } public async Task GetUserAsync(int dxpUserId, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QuerySingleAsync( login, AuthQB.GET_USER_BY_ID, new { DxpUserId = dxpUserId }, cancellationToken: ct).ConfigureAwait(false); } public async Task GetUserByEmailAsync(string email, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QuerySingleAsync( login, AuthQB.GET_USER_BY_EMAIL, new { Email = email }, cancellationToken: ct).ConfigureAwait(false); } public async Task SaveUserAsync(UserDTO dto, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _QueryExecutor.ExecuteAsync( login, AuthQB.INSERT_USER, dto, tx, cancellationToken: ct).ConfigureAwait(false); } public async Task UpdateLoginStatsAsync(UserDTO dto, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _QueryExecutor.ExecuteAsync( login, AuthQB.UPDATE_LOGIN_STATS, dto, tx, cancellationToken: ct).ConfigureAwait(false); } public async Task> GetMyContextsAsync(int dxpUserId, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QueryAsync( login, AuthQB.GET_MY_CONTEXTS, new { DxpUserId = dxpUserId }, cancellationToken: ct).ConfigureAwait(false) ?? Enumerable.Empty(); } public async Task GetUserPartyRoleAsync(int dxpUserPartyRoleId, int dxpUserId, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QuerySingleAsync( login, AuthQB.GET_USER_PARTY_ROLE_BY_ID, new { DxpUserPartyRoleId = dxpUserPartyRoleId, DxpUserId = dxpUserId }, cancellationToken: ct).ConfigureAwait(false); } public async Task SaveUserPartyRoleAsync(UserPartyRoleDTO dto, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _QueryExecutor.ExecuteAsync( login, AuthQB.INSERT_USER_PARTY_ROLE, dto, tx, cancellationToken: ct).ConfigureAwait(false); } public async Task GetRefreshTokenByIdAsync(int dxpRefreshTokenId, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QuerySingleAsync( login, AuthQB.GET_REFRESH_TOKEN_BY_ID, new { DxpRefreshTokenId = dxpRefreshTokenId }, cancellationToken: ct).ConfigureAwait(false); } public async Task GetRefreshTokenByHashAsync(string tokenHash, LoginDTO login, CancellationToken ct) { return await _QueryExecutor.QuerySingleAsync( login, AuthQB.GET_REFRESH_TOKEN_BY_HASH, new { TokenHash = tokenHash }, cancellationToken: ct).ConfigureAwait(false); } public async Task SaveRefreshTokenAsync(RefreshTokenDTO dto, LoginDTO login, DbTransaction tx, CancellationToken ct) { await _QueryExecutor.ExecuteAsync( login, AuthQB.INSERT_REFRESH_TOKEN, dto, tx, cancellationToken: ct).ConfigureAwait(false); } public async Task RevokeRefreshTokenAsync(int dxpRefreshTokenId, int? replacedByTokenId, LoginDTO login, DbTransaction tx, CancellationToken ct) { var param = new { DxpRefreshTokenId = dxpRefreshTokenId, RevokedOn = DateTime.UtcNow, ReplacedByTokenId = replacedByTokenId }; await _QueryExecutor.ExecuteAsync( login, AuthQB.REVOKE_REFRESH_TOKEN, param, tx, cancellationToken: ct).ConfigureAwait(false); } public async Task> GetChainDescendantsAsync(int dxpRefreshTokenId, LoginDTO login, CancellationToken ct) { var descendants = new List(); var current = await GetRefreshTokenByIdAsync(dxpRefreshTokenId, login, ct).ConfigureAwait(false); var nextId = current?.ReplacedByTokenId; // Defensive cycle guard — ReplacedByTokenId should never form a loop, but a single PK // lookup per hop is cheap insurance against an unbounded walk if it somehow did. var visited = new HashSet { dxpRefreshTokenId }; while (nextId.HasValue && visited.Add(nextId.Value)) { var next = await GetRefreshTokenByIdAsync(nextId.Value, login, ct).ConfigureAwait(false); if (next is null) break; descendants.Add(next); nextId = next.ReplacedByTokenId; } return descendants; } }