179 lines
7.4 KiB
C#
179 lines
7.4 KiB
C#
using System.Threading.Tasks;
|
|
using AccessQueueService.Data;
|
|
using AccessQueueService.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AccessQueueService.Services
|
|
{
|
|
public class AccessService : IAccessService
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly IAccessQueueRepo _accessQueueRepo;
|
|
private readonly ILogger<AccessService> _logger;
|
|
|
|
private readonly SemaphoreSlim _queueLock = new(1, 1);
|
|
private AccessQueueConfig _config;
|
|
public AccessService(IConfiguration configuration, IAccessQueueRepo accessQueueRepo, ILogger<AccessService> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_accessQueueRepo = accessQueueRepo;
|
|
_logger = logger;
|
|
_config = new AccessQueueConfig
|
|
{
|
|
ExpirationSeconds = _configuration.GetValue<int>("AccessQueue:ExpirationSeconds"),
|
|
ActivitySeconds = _configuration.GetValue<int>("AccessQueue:ActivitySeconds"),
|
|
CapacityLimit = _configuration.GetValue<int>("AccessQueue:CapacityLimit"),
|
|
RollingExpiration = _configuration.GetValue<bool>("AccessQueue:RollingExpiration"),
|
|
CacheMilliseconds = _configuration.GetValue<int>("AccessQueue:CacheMilliseconds")
|
|
};
|
|
}
|
|
public AccessQueueConfig GetConfiguration() => _config.Clone();
|
|
public void UpdateConfiguration(AccessQueueConfig config)
|
|
{
|
|
_config = config.Clone();
|
|
}
|
|
public void PatchConfiguration(AccessQueueConfig partialConfig)
|
|
{
|
|
if (partialConfig.CapacityLimit.HasValue) _config.CapacityLimit = partialConfig.CapacityLimit.Value;
|
|
if (partialConfig.ActivitySeconds.HasValue) _config.ActivitySeconds = partialConfig.ActivitySeconds.Value;
|
|
if (partialConfig.ExpirationSeconds.HasValue) _config.ExpirationSeconds = partialConfig.ExpirationSeconds.Value;
|
|
if (partialConfig.RollingExpiration.HasValue) _config.RollingExpiration = partialConfig.RollingExpiration.Value;
|
|
}
|
|
public int UnexpiredTicketsCount => _accessQueueRepo.GetUnexpiredTicketsCount();
|
|
public int ActiveTicketsCount => _accessQueueRepo.GetActiveTicketsCount(DateTime.UtcNow.AddSeconds(-_config.ActivitySeconds.Value));
|
|
public int QueueCount => _accessQueueRepo.GetQueueCount();
|
|
public AccessQueueStatus Status => new()
|
|
{
|
|
ActiveTicketsCount = this.ActiveTicketsCount,
|
|
QueueCount = this.QueueCount,
|
|
UnexpiredTicketsCount = this.UnexpiredTicketsCount,
|
|
};
|
|
|
|
public async Task<AccessResponse> RequestAccess(string userId)
|
|
{
|
|
await _queueLock.WaitAsync();
|
|
try
|
|
{
|
|
var hasCapacity = !_accessQueueRepo.DidDequeueUntilFull(_config);
|
|
var existingTicket = _accessQueueRepo.GetTicket(userId);
|
|
if (existingTicket != null && existingTicket.ExpiresOn > DateTime.UtcNow)
|
|
{
|
|
// Already has access
|
|
var expiresOn = existingTicket.ExpiresOn;
|
|
if (_config.RollingExpiration.Value)
|
|
{
|
|
expiresOn = DateTime.UtcNow.AddSeconds(_config.ExpirationSeconds.Value);
|
|
}
|
|
_accessQueueRepo.UpsertTicket(new AccessTicket
|
|
{
|
|
UserId = userId,
|
|
ExpiresOn = expiresOn,
|
|
LastActive = DateTime.UtcNow
|
|
});
|
|
_logger.LogInformation("User {UserId} already has access. Expires on {ExpiresOn}.", userId, expiresOn);
|
|
return new AccessResponse
|
|
{
|
|
ExpiresOn = expiresOn
|
|
};
|
|
}
|
|
if (hasCapacity)
|
|
{
|
|
// Doesn't have access, but there's space available
|
|
var accessTicket = new AccessTicket
|
|
{
|
|
UserId = userId,
|
|
ExpiresOn = DateTime.UtcNow.AddSeconds(_config.ExpirationSeconds.Value),
|
|
LastActive = DateTime.UtcNow
|
|
};
|
|
_accessQueueRepo.UpsertTicket(accessTicket);
|
|
_logger.LogInformation("User {UserId} granted access. Expires on {ExpiresOn}.", userId, accessTicket.ExpiresOn);
|
|
return new AccessResponse
|
|
{
|
|
ExpiresOn = accessTicket.ExpiresOn,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// No access and no space, add to queue
|
|
var requestsAhead = _accessQueueRepo.GetRequestsAhead(userId);
|
|
if (requestsAhead == -1)
|
|
{
|
|
requestsAhead = _accessQueueRepo.GetQueueCount();
|
|
_accessQueueRepo.Enqueue(new AccessTicket
|
|
{
|
|
UserId = userId,
|
|
LastActive = DateTime.UtcNow,
|
|
ExpiresOn = DateTime.MaxValue,
|
|
});
|
|
_logger.LogInformation("User {UserId} added to queue. Requests ahead: {RequestsAhead}.", userId, requestsAhead);
|
|
}
|
|
else
|
|
{
|
|
_logger.LogInformation("User {UserId} already in queue. Requests ahead: {RequestsAhead}.", userId, requestsAhead);
|
|
}
|
|
return new AccessResponse
|
|
{
|
|
ExpiresOn = null,
|
|
RequestsAhead = requestsAhead
|
|
};
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occurred while processing access request for user {UserId}.", userId);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
_queueLock.Release();
|
|
}
|
|
}
|
|
|
|
public async Task<bool> RevokeAccess(string userId)
|
|
{
|
|
await _queueLock.WaitAsync();
|
|
try
|
|
{
|
|
var removed = _accessQueueRepo.RemoveUser(userId);
|
|
if (removed)
|
|
{
|
|
_logger.LogInformation("User {UserId} access revoked.", userId);
|
|
}
|
|
return removed;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occurred while revoking access for user {UserId}.", userId);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
_queueLock.Release();
|
|
}
|
|
}
|
|
|
|
public async Task<int> DeleteExpiredTickets()
|
|
{
|
|
await _queueLock.WaitAsync();
|
|
try
|
|
{
|
|
var removed = _accessQueueRepo.DeleteExpiredTickets();
|
|
if (removed > 0)
|
|
{
|
|
_logger.LogInformation("Cleaned up {Count} expired tickets.", removed);
|
|
}
|
|
return removed;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Exception occurred during expired ticket cleanup.");
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
_queueLock.Release();
|
|
}
|
|
}
|
|
}
|
|
}
|