diff --git a/src/XtremeIdiots.Portal.Repository.Api.Tests.V1/Controllers/V1/DataMaintenanceControllerTests.cs b/src/XtremeIdiots.Portal.Repository.Api.Tests.V1/Controllers/V1/DataMaintenanceControllerTests.cs index 84bc0530..e64497f1 100644 --- a/src/XtremeIdiots.Portal.Repository.Api.Tests.V1/Controllers/V1/DataMaintenanceControllerTests.cs +++ b/src/XtremeIdiots.Portal.Repository.Api.Tests.V1/Controllers/V1/DataMaintenanceControllerTests.cs @@ -1,4 +1,5 @@ using System.Net; +using Microsoft.Extensions.Configuration; using Xunit; using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1; using XtremeIdiots.Portal.Repository.Abstractions.Interfaces.V1; @@ -10,15 +11,17 @@ namespace XtremeIdiots.Portal.Repository.Api.Tests.V1.Controllers.V1; public class DataMaintenanceControllerTests { + private static readonly IConfiguration EmptyConfiguration = new ConfigurationBuilder().Build(); + private DataMaintenanceController CreateController(PortalDbContext context) { - return new DataMaintenanceController(context); + return new DataMaintenanceController(context, EmptyConfiguration); } [Fact] public void Constructor_WithNullContext_ThrowsArgumentNullException() { - Assert.Throws(() => new DataMaintenanceController(null!)); + Assert.Throws(() => new DataMaintenanceController(null!, EmptyConfiguration)); } [Fact(Skip = "Uses ExecuteSqlInterpolatedAsync which is not supported by the InMemory provider")] diff --git a/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/DataMaintenanceController.cs b/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/DataMaintenanceController.cs index 4f9d60eb..398c07bf 100644 --- a/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/DataMaintenanceController.cs +++ b/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/DataMaintenanceController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using System.Linq; using System.Net; using Azure.Identity; @@ -29,16 +30,18 @@ namespace XtremeIdiots.Portal.RepositoryWebApi.Controllers.V1; public class DataMaintenanceController : ControllerBase, IDataMaintenanceApi { private readonly PortalDbContext context; + private readonly IConfiguration configuration; /// /// Initializes a new instance of the class. /// /// The database context for data operations. /// Thrown when context is null. - public DataMaintenanceController(PortalDbContext context) + public DataMaintenanceController(PortalDbContext context, IConfiguration configuration) { ArgumentNullException.ThrowIfNull(context); this.context = context; + this.configuration = configuration; } /// @@ -144,7 +147,8 @@ public async Task PruneRecentPlayers(CancellationToken cancellati /// An API result indicating the operation completed successfully. async Task IDataMaintenanceApi.PruneRecentPlayers(CancellationToken cancellationToken) { - var cutoffDate = DateTime.UtcNow.AddDays(-7); + var recentPlayersDays = int.TryParse(configuration["DataRetention:RecentPlayersDays"], out var rpd) ? rpd : 7; + var cutoffDate = DateTime.UtcNow.AddDays(-recentPlayersDays); await context.Database.ExecuteSqlInterpolatedAsync($"DELETE FROM [dbo].[RecentPlayers] WHERE [Timestamp] < {cutoffDate}", cancellationToken).ConfigureAwait(false); return new ApiResponse().ToApiResult(); } @@ -172,7 +176,8 @@ public async Task ResetSystemAssignedPlayerTags(CancellationToken /// An API result indicating the operation completed successfully. async Task IDataMaintenanceApi.ResetSystemAssignedPlayerTags(CancellationToken cancellationToken) { - var twoWeeksAgo = DateTime.UtcNow.AddDays(-14); + var inactivePlayerDays = int.TryParse(configuration["DataRetention:InactivePlayerDays"], out var ipd) ? ipd : 14; + var twoWeeksAgo = DateTime.UtcNow.AddDays(-inactivePlayerDays); // Get the tag IDs by name var activeTag = await context.Tags diff --git a/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/GameTrackerBannerController.cs b/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/GameTrackerBannerController.cs index 465436aa..f9e72656 100644 --- a/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/GameTrackerBannerController.cs +++ b/src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/GameTrackerBannerController.cs @@ -134,7 +134,8 @@ private async Task> UpdateBannerImageAndRedirect bool gametrackerFallback, CancellationToken cancellationToken) { - var gameTrackerImageUrl = $"https://cache.gametracker.com/server_info/{ipAddress}:{queryPort}/{imageName}"; + var gameTrackerBannerBaseUrl = (configuration["GameTracker:BannerBaseUrl"] ?? "https://cache.gametracker.com/server_info/").TrimEnd('/') + "/"; + var gameTrackerImageUrl = $"{gameTrackerBannerBaseUrl}{ipAddress}:{queryPort}/{imageName}"; try { diff --git a/src/XtremeIdiots.Portal.Repository.Api.V1/Program.cs b/src/XtremeIdiots.Portal.Repository.Api.V1/Program.cs index d4646af2..8df3878f 100644 --- a/src/XtremeIdiots.Portal.Repository.Api.V1/Program.cs +++ b/src/XtremeIdiots.Portal.Repository.Api.V1/Program.cs @@ -11,31 +11,32 @@ using XtremeIdiots.Portal.Repository.Api.V1; using Asp.Versioning; using XtremeIdiots.Portal.Repository.Api.V1.OpenApi; -using Azure.Core; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); -var appConfigurationEndpoint = builder.Configuration["AzureAppConfiguration:Endpoint"]; +var appConfigEndpoint = builder.Configuration["AzureAppConfiguration:Endpoint"]; var isAzureAppConfigurationEnabled = false; -if (!string.IsNullOrWhiteSpace(appConfigurationEndpoint)) +if (!string.IsNullOrWhiteSpace(appConfigEndpoint)) { var managedIdentityClientId = builder.Configuration["AzureAppConfiguration:ManagedIdentityClientId"]; - TokenCredential identityCredential = string.IsNullOrWhiteSpace(managedIdentityClientId) - ? new DefaultAzureCredential() - : new ManagedIdentityCredential(managedIdentityClientId); + var environmentLabel = builder.Configuration["AzureAppConfiguration:Environment"]; + + var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions + { + ManagedIdentityClientId = managedIdentityClientId, + }); builder.Configuration.AddAzureAppConfiguration(options => { - options.Connect(new Uri(appConfigurationEndpoint), identityCredential) - .Select("XtremeIdiots.Portal.Repository.Api.V1:*", labelFilter: builder.Configuration["AzureAppConfiguration:Environment"]) - .TrimKeyPrefix("XtremeIdiots.Portal.Repository.Api.V1:"); + options.Connect(new Uri(appConfigEndpoint), credential) + .Select("XtremeIdiots.Portal.Repository.Api.V1:*", environmentLabel) + .TrimKeyPrefix("XtremeIdiots.Portal.Repository.Api.V1:") + .Select("GameTracker:*", environmentLabel) + .Select("SqlResilience:*", environmentLabel); - options.ConfigureKeyVault(keyVaultOptions => - { - keyVaultOptions.SetCredential(identityCredential); - }); + options.ConfigureKeyVault(kv => kv.SetCredential(credential)); }); builder.Services.AddAzureAppConfiguration(); @@ -57,9 +58,9 @@ telemetryProcessorChainBuilder.UseAdaptiveSampling( settings: new SamplingPercentageEstimatorSettings { - InitialSamplingPercentage = 5, - MinSamplingPercentage = 5, - MaxSamplingPercentage = 60 + InitialSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:InitialSamplingPercentage"], out var initPct) ? initPct : 5, + MinSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MinSamplingPercentage"], out var minPct) ? minPct : 5, + MaxSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MaxSamplingPercentage"], out var maxPct) ? maxPct : 60 }, callback: null, excludedTypes: "Exception"); @@ -75,10 +76,14 @@ builder.Services.AddDbContext(options => { + var retryCount = int.TryParse(builder.Configuration["SqlResilience:RetryCount"], out var rc) ? rc : 3; + var retryDelay = int.TryParse(builder.Configuration["SqlResilience:RetryDelaySeconds"], out var rd) ? rd : 5; + var commandTimeout = int.TryParse(builder.Configuration["SqlResilience:CommandTimeoutSeconds"], out var ct) ? ct : 180; + options.UseSqlServer(builder.Configuration["sql_connection_string"], sqlOptions => { - sqlOptions.EnableRetryOnFailure(3, TimeSpan.FromSeconds(5), null); - sqlOptions.CommandTimeout(180); + sqlOptions.EnableRetryOnFailure(retryCount, TimeSpan.FromSeconds(retryDelay), null); + sqlOptions.CommandTimeout(commandTimeout); }); }); diff --git a/src/XtremeIdiots.Portal.Repository.Api.V2/Program.cs b/src/XtremeIdiots.Portal.Repository.Api.V2/Program.cs index 018ea0c3..ac2596db 100644 --- a/src/XtremeIdiots.Portal.Repository.Api.V2/Program.cs +++ b/src/XtremeIdiots.Portal.Repository.Api.V2/Program.cs @@ -11,31 +11,31 @@ using XtremeIdiots.Portal.Repository.Api.V2; using Asp.Versioning; using XtremeIdiots.Portal.Repository.Api.V2.OpenApi; -using Azure.Core; using Scalar.AspNetCore; var builder = WebApplication.CreateBuilder(args); -var appConfigurationEndpoint = builder.Configuration["AzureAppConfiguration:Endpoint"]; +var appConfigEndpoint = builder.Configuration["AzureAppConfiguration:Endpoint"]; var isAzureAppConfigurationEnabled = false; -if (!string.IsNullOrWhiteSpace(appConfigurationEndpoint)) +if (!string.IsNullOrWhiteSpace(appConfigEndpoint)) { var managedIdentityClientId = builder.Configuration["AzureAppConfiguration:ManagedIdentityClientId"]; - TokenCredential identityCredential = string.IsNullOrWhiteSpace(managedIdentityClientId) - ? new DefaultAzureCredential() - : new ManagedIdentityCredential(managedIdentityClientId); + var environmentLabel = builder.Configuration["AzureAppConfiguration:Environment"]; + + var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions + { + ManagedIdentityClientId = managedIdentityClientId, + }); builder.Configuration.AddAzureAppConfiguration(options => { - options.Connect(new Uri(appConfigurationEndpoint), identityCredential) - .Select("XtremeIdiots.Portal.Repository.Api.V2:*", labelFilter: builder.Configuration["AzureAppConfiguration:Environment"]) - .TrimKeyPrefix("XtremeIdiots.Portal.Repository.Api.V2:"); + options.Connect(new Uri(appConfigEndpoint), credential) + .Select("XtremeIdiots.Portal.Repository.Api.V2:*", environmentLabel) + .TrimKeyPrefix("XtremeIdiots.Portal.Repository.Api.V2:") + .Select("SqlResilience:*", environmentLabel); - options.ConfigureKeyVault(keyVaultOptions => - { - keyVaultOptions.SetCredential(identityCredential); - }); + options.ConfigureKeyVault(kv => kv.SetCredential(credential)); }); builder.Services.AddAzureAppConfiguration(); @@ -54,9 +54,9 @@ telemetryProcessorChainBuilder.UseAdaptiveSampling( settings: new SamplingPercentageEstimatorSettings { - InitialSamplingPercentage = 5, - MinSamplingPercentage = 5, - MaxSamplingPercentage = 60 + InitialSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:InitialSamplingPercentage"], out var initPct) ? initPct : 5, + MinSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MinSamplingPercentage"], out var minPct) ? minPct : 5, + MaxSamplingPercentage = double.TryParse(builder.Configuration["ApplicationInsights:MaxSamplingPercentage"], out var maxPct) ? maxPct : 60 }, callback: null, excludedTypes: "Exception"); @@ -72,10 +72,14 @@ builder.Services.AddDbContext(options => { + var retryCount = int.TryParse(builder.Configuration["SqlResilience:RetryCount"], out var rc) ? rc : 3; + var retryDelay = int.TryParse(builder.Configuration["SqlResilience:RetryDelaySeconds"], out var rd) ? rd : 5; + var commandTimeout = int.TryParse(builder.Configuration["SqlResilience:CommandTimeoutSeconds"], out var ct) ? ct : 180; + options.UseSqlServer(builder.Configuration["sql_connection_string"], sqlOptions => { - sqlOptions.EnableRetryOnFailure(3, TimeSpan.FromSeconds(5), null); - sqlOptions.CommandTimeout(180); + sqlOptions.EnableRetryOnFailure(retryCount, TimeSpan.FromSeconds(retryDelay), null); + sqlOptions.CommandTimeout(commandTimeout); }); });