using System.Data.Common; using EntitlementDAL.DTOs; using EntitlementDAL.Interfaces; using EntitlementDAL.QueryBuilders; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; namespace EntitlementDAL.Implementations; public class ClientRefreshTokenDAL : IClientRefreshTokenDAL { private readonly IQueryExecutor _QueryExecutor; public ClientRefreshTokenDAL(IQueryExecutor queryExecutor) => _QueryExecutor = queryExecutor; public async Task GetByIdAsync(int clientRefreshTokenId, LoginDTO login, CancellationToken ct) => await _QueryExecutor.QuerySingleAsync( login, ClientRefreshTokenQB.GET_BY_ID, new { ClientRefreshTokenId = clientRefreshTokenId }, cancellationToken: ct).ConfigureAwait(false); public async Task GetByTokenHashAsync(string tokenHash, LoginDTO login, CancellationToken ct) => await _QueryExecutor.QuerySingleAsync( login, ClientRefreshTokenQB.GET_BY_TOKEN_HASH, new { TokenHash = tokenHash }, cancellationToken: ct).ConfigureAwait(false); public async Task SaveAsync(ClientRefreshTokenDTO dto, LoginDTO login, DbTransaction tx, CancellationToken ct) => await _QueryExecutor.ExecuteAsync(login, ClientRefreshTokenQB.INSERT, dto, tx, cancellationToken: ct).ConfigureAwait(false); public async Task RevokeAsync(int clientRefreshTokenId, int? replacedByTokenId, LoginDTO login, DbTransaction tx, CancellationToken ct) => await _QueryExecutor.ExecuteAsync(login, ClientRefreshTokenQB.REVOKE, new { ClientRefreshTokenId = clientRefreshTokenId, RevokedOn = DateTime.UtcNow, ReplacedByTokenId = replacedByTokenId }, tx, cancellationToken: ct).ConfigureAwait(false); public async Task> GetChainDescendantsAsync(int clientRefreshTokenId, LoginDTO login, CancellationToken ct) { var descendants = new List(); var current = await GetByIdAsync(clientRefreshTokenId, 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 { clientRefreshTokenId }; while (nextId.HasValue && visited.Add(nextId.Value)) { var next = await GetByIdAsync(nextId.Value, login, ct).ConfigureAwait(false); if (next is null) break; descendants.Add(next); nextId = next.ReplacedByTokenId; } return descendants; } }