From fbbd5933ed90d027378f1ae8d3cd6ff133d6f53e Mon Sep 17 00:00:00 2001 From: henry Date: Sun, 18 May 2025 03:21:26 +0000 Subject: [PATCH] #17 Make configuration eidtable at runtime (#18) Reviewed-on: https://git.hobbs.zone/henry/AccessQueueService/pulls/18 --- .../Components/Layout/MainLayout.razor | 3 + .../Components/Pages/Config.razor | 96 +++++++++++++++++++ .../Components/Pages/Home.razor | 4 +- .../Models/AccessQueueConfig.cs | 10 -- .../Services/AccessQueueManager.cs | 10 +- .../Services/IAccessQueueManager.cs | 2 + .../Controllers/AccessController.cs | 13 +++ .../Models/AccessQueueConfig.cs | 15 +++ AccessQueueService/Services/AccessService.cs | 41 +++++--- AccessQueueService/Services/IAccessService.cs | 3 + 10 files changed, 168 insertions(+), 29 deletions(-) create mode 100644 AccessQueuePlayground/Components/Pages/Config.razor delete mode 100644 AccessQueuePlayground/Models/AccessQueueConfig.cs create mode 100644 AccessQueueService/Models/AccessQueueConfig.cs diff --git a/AccessQueuePlayground/Components/Layout/MainLayout.razor b/AccessQueuePlayground/Components/Layout/MainLayout.razor index e21aaad..ee658cd 100644 --- a/AccessQueuePlayground/Components/Layout/MainLayout.razor +++ b/AccessQueuePlayground/Components/Layout/MainLayout.razor @@ -14,6 +14,9 @@ + diff --git a/AccessQueuePlayground/Components/Pages/Config.razor b/AccessQueuePlayground/Components/Pages/Config.razor new file mode 100644 index 0000000..05288e2 --- /dev/null +++ b/AccessQueuePlayground/Components/Pages/Config.razor @@ -0,0 +1,96 @@ +@page "/config" +@inject AccessQueuePlayground.Services.IAccessQueueManager QueueManager +@using BlazorBootstrap + +

Access Queue Configuration

+ + + + +
+ + + @if (!isCapacityLimitValid) + { +
Please enter a positive integer.
+ } +
+
+ + + @if (!isActivitySecondsValid) + { +
Please enter a positive integer.
+ } +
+
+ + + @if (!isExpirationSecondsValid) + { +
Please enter a positive integer.
+ } +
+
+ +
+ + @if (successMessage != null) + { + @successMessage + } +
+ +@code { + private ConfigModel config = new(); + private bool isCapacityLimitValid = true; + private bool isActivitySecondsValid = true; + private bool isExpirationSecondsValid = true; + private string? successMessage; + + protected override void OnInitialized() + { + var current = QueueManager.GetConfig(); + config = new ConfigModel + { + ActivitySeconds = (current.ActivitySeconds ?? 0).ToString(), + CapacityLimit = (current.CapacityLimit ?? 0).ToString(), + ExpirationSeconds = (current.ExpirationSeconds ?? 0).ToString(), + RollingExpiration = current.RollingExpiration ?? false + }; + ValidateInputs(); + } + + private bool IsFormValid => isCapacityLimitValid && isActivitySecondsValid && isExpirationSecondsValid; + + private void ValidateInputs() + { + isCapacityLimitValid = int.TryParse(config.CapacityLimit, out var cap) && cap > 0; + isActivitySecondsValid = int.TryParse(config.ActivitySeconds, out var act) && act > 0; + isExpirationSecondsValid = int.TryParse(config.ExpirationSeconds, out var exp) && exp > 0; + } + + private async Task HandleValidSubmit() + { + successMessage = null; + ValidateInputs(); + if (!IsFormValid) + return; + await Task.Run(() => QueueManager.UpdateConfig(new () + { + ActivitySeconds = int.Parse(config.ActivitySeconds), + CapacityLimit = int.Parse(config.CapacityLimit), + ExpirationSeconds = int.Parse(config.ExpirationSeconds), + RollingExpiration = config.RollingExpiration + })); + successMessage = "Configuration updated successfully."; + } + + public class ConfigModel + { + public string CapacityLimit { get; set; } = ""; + public string ActivitySeconds { get; set; } = ""; + public string ExpirationSeconds { get; set; } = ""; + public bool RollingExpiration { get; set; } + } +} diff --git a/AccessQueuePlayground/Components/Pages/Home.razor b/AccessQueuePlayground/Components/Pages/Home.razor index d29a360..2533d95 100644 --- a/AccessQueuePlayground/Components/Pages/Home.razor +++ b/AccessQueuePlayground/Components/Pages/Home.razor @@ -1,6 +1,7 @@ @page "/" @using AccessQueuePlayground.Models @using AccessQueuePlayground.Services +@using AccessQueueService.Models; @using BlazorBootstrap @inject IAccessQueueManager Manager @@ -12,7 +13,8 @@

Expiration Seconds: @Config.ExpirationSeconds, Activity Seconds: @Config.ActivitySeconds, - Capacity Limit: @Config.CapacityLimit + Capacity Limit: @Config.CapacityLimit, + Rolling Expiration: @Config.RollingExpiration

}

diff --git a/AccessQueuePlayground/Models/AccessQueueConfig.cs b/AccessQueuePlayground/Models/AccessQueueConfig.cs deleted file mode 100644 index 3bf62ef..0000000 --- a/AccessQueuePlayground/Models/AccessQueueConfig.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AccessQueuePlayground.Models -{ - public class AccessQueueConfig - { - public int ActivitySeconds { get; set; } - public int ExpirationSeconds { get; set; } - public int CapacityLimit { get; set; } - - } -} diff --git a/AccessQueuePlayground/Services/AccessQueueManager.cs b/AccessQueuePlayground/Services/AccessQueueManager.cs index 5412821..4f17277 100644 --- a/AccessQueuePlayground/Services/AccessQueueManager.cs +++ b/AccessQueuePlayground/Services/AccessQueueManager.cs @@ -29,12 +29,12 @@ namespace AccessQueuePlayground.Services public AccessQueueStatus GetStatus() => _status; - public AccessQueueConfig GetConfig() => new AccessQueueConfig + public AccessQueueConfig GetConfig() => _accessService.GetConfiguration(); + + public void UpdateConfig(AccessQueueConfig config) { - ActivitySeconds = _config.GetValue("AccessQueue:ActivitySeconds"), - ExpirationSeconds = _config.GetValue("AccessQueue:ExpirationSeconds"), - CapacityLimit = _config.GetValue("AccessQueue:CapacityLimit") - }; + _accessService.UpdateConfiguration(config); + } public Guid AddUser(bool isActive) { diff --git a/AccessQueuePlayground/Services/IAccessQueueManager.cs b/AccessQueuePlayground/Services/IAccessQueueManager.cs index fcdfd52..362955f 100644 --- a/AccessQueuePlayground/Services/IAccessQueueManager.cs +++ b/AccessQueuePlayground/Services/IAccessQueueManager.cs @@ -1,4 +1,5 @@ using AccessQueuePlayground.Models; +using AccessQueueService.Models; namespace AccessQueuePlayground.Services { @@ -6,6 +7,7 @@ namespace AccessQueuePlayground.Services { public event Action? StatusUpdated; public AccessQueueConfig GetConfig(); + public void UpdateConfig(AccessQueueConfig config); public Task RecalculateStatus(); public AccessQueueStatus GetStatus(); public Guid AddUser(bool isActive); diff --git a/AccessQueueService/Controllers/AccessController.cs b/AccessQueueService/Controllers/AccessController.cs index f9fcc05..d1f4786 100644 --- a/AccessQueueService/Controllers/AccessController.cs +++ b/AccessQueueService/Controllers/AccessController.cs @@ -28,5 +28,18 @@ namespace AccessQueueService.Controllers { return await _accessService.RevokeAccess(id); } + + [HttpGet("configuration")] + public ActionResult GetConfiguration() + { + return Ok(_accessService.GetConfiguration()); + } + + [HttpPost("configuration")] + public IActionResult UpdateConfiguration([FromBody] AccessQueueConfig config) + { + _accessService.PatchConfiguration(config); + return NoContent(); + } } } diff --git a/AccessQueueService/Models/AccessQueueConfig.cs b/AccessQueueService/Models/AccessQueueConfig.cs new file mode 100644 index 0000000..413caca --- /dev/null +++ b/AccessQueueService/Models/AccessQueueConfig.cs @@ -0,0 +1,15 @@ +namespace AccessQueueService.Models +{ + public class AccessQueueConfig + { + public int? CapacityLimit { get; set; } + public int? ActivitySeconds { get; set; } + public int? ExpirationSeconds { get; set; } + public bool? RollingExpiration { get; set; } + + public AccessQueueConfig Clone() + { + return (AccessQueueConfig)this.MemberwiseClone(); + } + } +} diff --git a/AccessQueueService/Services/AccessService.cs b/AccessQueueService/Services/AccessService.cs index 65d0f9a..891ab88 100644 --- a/AccessQueueService/Services/AccessService.cs +++ b/AccessQueueService/Services/AccessService.cs @@ -12,37 +12,52 @@ namespace AccessQueueService.Services private readonly ILogger _logger; private readonly SemaphoreSlim _queueLock = new(1, 1); - private readonly int EXP_SECONDS; - private readonly int ACT_SECONDS; - private readonly int CAPACITY_LIMIT; - private readonly bool ROLLING_EXPIRATION; + private AccessQueueConfig _config; public AccessService(IConfiguration configuration, IAccessQueueRepo accessQueueRepo, ILogger logger) { _configuration = configuration; _accessQueueRepo = accessQueueRepo; _logger = logger; - EXP_SECONDS = _configuration.GetValue("AccessQueue:ExpirationSeconds"); - ACT_SECONDS = _configuration.GetValue("AccessQueue:ActivitySeconds"); - CAPACITY_LIMIT = _configuration.GetValue("AccessQueue:CapacityLimit"); - ROLLING_EXPIRATION = _configuration.GetValue("AccessQueue:RollingExpiration"); + _config = new AccessQueueConfig + { + ExpirationSeconds = _configuration.GetValue("AccessQueue:ExpirationSeconds"), + ActivitySeconds = _configuration.GetValue("AccessQueue:ActivitySeconds"), + CapacityLimit = _configuration.GetValue("AccessQueue:CapacityLimit"), + RollingExpiration = _configuration.GetValue("AccessQueue:RollingExpiration") + }; + } + 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(-_configuration.GetValue("AccessQueue:ActivitySeconds"))); + public int ActiveTicketsCount => _accessQueueRepo.GetActiveTicketsCount(DateTime.UtcNow.AddSeconds(-_config.ActivitySeconds.Value)); public int QueueCount => _accessQueueRepo.GetQueueCount(); public async Task RequestAccess(string userId) { await _queueLock.WaitAsync(); try { - var hasCapacity = !_accessQueueRepo.DidDequeueUntilFull(ACT_SECONDS, EXP_SECONDS, CAPACITY_LIMIT); + var hasCapacity = !_accessQueueRepo.DidDequeueUntilFull( + _config.ActivitySeconds.Value, + _config.ExpirationSeconds.Value, + _config.CapacityLimit.Value); var existingTicket = _accessQueueRepo.GetTicket(userId); if (existingTicket != null && existingTicket.ExpiresOn > DateTime.UtcNow) { // Already has access var expiresOn = existingTicket.ExpiresOn; - if (ROLLING_EXPIRATION) + if (_config.RollingExpiration.Value) { - expiresOn = DateTime.UtcNow.AddSeconds(EXP_SECONDS); + expiresOn = DateTime.UtcNow.AddSeconds(_config.ExpirationSeconds.Value); } _accessQueueRepo.UpsertTicket(new AccessTicket { @@ -62,7 +77,7 @@ namespace AccessQueueService.Services var accessTicket = new AccessTicket { UserId = userId, - ExpiresOn = DateTime.UtcNow.AddSeconds(EXP_SECONDS), + ExpiresOn = DateTime.UtcNow.AddSeconds(_config.ExpirationSeconds.Value), LastActive = DateTime.UtcNow }; _accessQueueRepo.UpsertTicket(accessTicket); diff --git a/AccessQueueService/Services/IAccessService.cs b/AccessQueueService/Services/IAccessService.cs index 6b94042..35cdf3c 100644 --- a/AccessQueueService/Services/IAccessService.cs +++ b/AccessQueueService/Services/IAccessService.cs @@ -7,5 +7,8 @@ namespace AccessQueueService.Services public Task RequestAccess(string userId); public Task RevokeAccess(string userId); public Task DeleteExpiredTickets(); + public AccessQueueConfig GetConfiguration(); + public void UpdateConfiguration(AccessQueueConfig config); + public void PatchConfiguration(AccessQueueConfig partialConfig); } }