diff --git a/API/Controller/Account/Login.cs b/API/Controller/Account/Login.cs index f8027a12..a95bed51 100644 --- a/API/Controller/Account/Login.cs +++ b/API/Controller/Account/Login.cs @@ -28,10 +28,10 @@ public sealed partial class AccountController [MapToApiVersion("1")] public async Task Login( [FromBody] Login body, - [FromServices] IOptions options, + [FromServices] FrontendOptions options, CancellationToken cancellationToken) { - var cookieDomainToUse = options.Value.CookieDomain.Split(',').FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); + var cookieDomainToUse = options.CookieDomains.FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); if (cookieDomainToUse is null) return Problem(LoginError.InvalidDomain); var loginAction = await _accountService.CreateUserLoginSessionAsync(body.Email, body.Password, new LoginContext diff --git a/API/Controller/Account/LoginV2.cs b/API/Controller/Account/LoginV2.cs index 6ab58532..6896227d 100644 --- a/API/Controller/Account/LoginV2.cs +++ b/API/Controller/Account/LoginV2.cs @@ -33,10 +33,10 @@ public sealed partial class AccountController public async Task LoginV2( [FromBody] LoginV2 body, [FromServices] ICloudflareTurnstileService turnstileService, - [FromServices] IOptions options, + [FromServices] FrontendOptions options, CancellationToken cancellationToken) { - var cookieDomainToUse = options.Value.CookieDomain.Split(',').FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); + var cookieDomainToUse = options.CookieDomains.FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); if (cookieDomainToUse is null) return Problem(LoginError.InvalidDomain); var remoteIP = HttpContext.GetRemoteIP(); diff --git a/API/Controller/Account/Logout.cs b/API/Controller/Account/Logout.cs index d689299d..35eb3f43 100644 --- a/API/Controller/Account/Logout.cs +++ b/API/Controller/Account/Logout.cs @@ -14,10 +14,8 @@ public sealed partial class AccountController [MapToApiVersion("1")] public async Task Logout( [FromServices] ISessionService sessionService, - [FromServices] IOptions options) + [FromServices] FrontendOptions options) { - var config = options.Value; - // Remove session if valid if (HttpContext.TryGetUserSessionToken(out var sessionToken)) { @@ -25,14 +23,14 @@ public async Task Logout( } // Make sure cookie is removed, no matter if authenticated or not - var cookieDomainToUse = config.CookieDomain.Split(',').FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); + var cookieDomainToUse = options.CookieDomains.FirstOrDefault(domain => Request.Headers.Host.ToString().EndsWith(domain, StringComparison.OrdinalIgnoreCase)); if (cookieDomainToUse is not null) { HttpContext.RemoveSessionKeyCookie("." + cookieDomainToUse); } else // Fallback to all domains { - foreach (var domain in config.CookieDomain.Split(',')) + foreach (var domain in options.CookieDomains) { HttpContext.RemoveSessionKeyCookie("." + domain); } diff --git a/API/Controller/Version/_ApiController.cs b/API/Controller/Version/_ApiController.cs index 9a49503b..1eced173 100644 --- a/API/Controller/Version/_ApiController.cs +++ b/API/Controller/Version/_ApiController.cs @@ -38,22 +38,19 @@ private static string GetBackendVersion() /// The version was successfully retrieved. [HttpGet] public LegacyDataResponse GetBackendInfo( - [FromServices] IOptions frontendOptions, - [FromServices] IOptions turnstileOptions + [FromServices] FrontendOptions frontendOptions, + [FromServices] TurnstileOptions turnstileOptions ) { - var frontendConfig = frontendOptions.Value; - var turnstileConfig = turnstileOptions.Value; - return new( new BackendInfoResponse { Version = OpenShockBackendVersion, Commit = GitHashAttribute.FullHash, CurrentTime = DateTimeOffset.UtcNow, - FrontendUrl = frontendConfig.BaseUrl, - ShortLinkUrl = frontendConfig.ShortUrl, - TurnstileSiteKey = turnstileConfig.SiteKey, + FrontendUrl = frontendOptions.BaseUrl, + ShortLinkUrl = frontendOptions.ShortUrl, + TurnstileSiteKey = turnstileOptions.SiteKey, IsUserAuthenticated = HttpContext.TryGetUserSessionToken(out _) }, "OpenShock" diff --git a/API/Program.cs b/API/Program.cs index f9b59e47..129fde32 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -1,7 +1,5 @@ using Microsoft.AspNetCore.Http.Connections; -using Microsoft.Extensions.Options; using OpenShock.API.Realtime; -using OpenShock.API.Services; using OpenShock.API.Services.Account; using OpenShock.API.Services.DeviceUpdate; using OpenShock.API.Services.Email; @@ -11,7 +9,6 @@ using OpenShock.Common.DeviceControl; using OpenShock.Common.Extensions; using OpenShock.Common.Hubs; -using OpenShock.Common.Options; using OpenShock.Common.Services; using OpenShock.Common.Services.Device; using OpenShock.Common.Services.LCGNodeProvisioner; @@ -21,23 +18,16 @@ var builder = OpenShockApplication.CreateDefaultBuilder(args); -#region Config - -builder.RegisterCommonOpenShockOptions(); - -builder.Services.Configure(builder.Configuration.GetRequiredSection(FrontendOptions.SectionName)); -builder.Services.AddSingleton, FrontendOptionsValidator>(); - -var databaseConfig = builder.Configuration.GetDatabaseOptions(); -var redisConfig = builder.Configuration.GetRedisConfigurationOptions(); - -#endregion +var redisOptions = builder.RegisterRedisOptions(); +var databaseOptions = builder.RegisterDatabaseOptions(); +builder.RegisterMetricsOptions(); +builder.RegisterFrontendOptions(); builder.Services - .AddOpenShockMemDB(redisConfig) - .AddOpenShockDB(databaseConfig) + .AddOpenShockMemDB(redisOptions) + .AddOpenShockDB(databaseOptions) .AddOpenShockServices() - .AddOpenShockSignalR(redisConfig); + .AddOpenShockSignalR(redisOptions); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -60,9 +50,9 @@ await app.UseCommonOpenShockMiddleware(); -if (!databaseConfig.SkipMigration) +if (!databaseOptions.SkipMigration) { - await app.ApplyPendingOpenShockMigrations(databaseConfig); + await app.ApplyPendingOpenShockMigrations(databaseOptions); } else { diff --git a/API/Services/Account/AccountService.cs b/API/Services/Account/AccountService.cs index 9bd816f9..019099c0 100644 --- a/API/Services/Account/AccountService.cs +++ b/API/Services/Account/AccountService.cs @@ -35,12 +35,12 @@ public sealed class AccountService : IAccountService /// /// public AccountService(OpenShockContext db, IEmailService emailService, - ISessionService sessionService, ILogger logger, IOptions options) + ISessionService sessionService, ILogger logger, FrontendOptions options) { _db = db; _emailService = emailService; _logger = logger; - _frontendConfig = options.Value; + _frontendConfig = options; _sessionService = sessionService; } diff --git a/API/Services/Email/EmailServiceExtension.cs b/API/Services/Email/EmailServiceExtension.cs index 6aba76c0..f45913bd 100644 --- a/API/Services/Email/EmailServiceExtension.cs +++ b/API/Services/Email/EmailServiceExtension.cs @@ -36,7 +36,7 @@ public static WebApplicationBuilder AddEmailService(this WebApplicationBuilder b private static WebApplicationBuilder AddSenderContactConfiguration(this WebApplicationBuilder builder) { - builder.Services.Configure(builder.Configuration.GetRequiredSection(MailOptions.SenderSectionName)); + builder.Services.AddSingleton(builder.Configuration.GetRequiredSection(MailOptions.SenderSectionName).Get() ?? throw new NullReferenceException()); return builder; } } diff --git a/API/Services/Email/Mailjet/MailjetEmailService.cs b/API/Services/Email/Mailjet/MailjetEmailService.cs index 09551327..776da1b1 100644 --- a/API/Services/Email/Mailjet/MailjetEmailService.cs +++ b/API/Services/Email/Mailjet/MailjetEmailService.cs @@ -11,8 +11,8 @@ public sealed class MailjetEmailService : IEmailService, IDisposable { private readonly HttpClient _httpClient; private readonly MailJetOptions _options; - private readonly ILogger _logger; private readonly MailOptions.MailSenderContact _sender; + private readonly ILogger _logger; /// /// DI Constructor @@ -23,14 +23,14 @@ public sealed class MailjetEmailService : IEmailService, IDisposable /// public MailjetEmailService( HttpClient httpClient, - IOptions options, - IOptions sender, + MailJetOptions options, + MailOptions.MailSenderContact sender, ILogger logger ) { _httpClient = httpClient; - _sender = sender.Value; - _options = options.Value; + _options = options; + _sender = sender; _logger = logger; } diff --git a/API/Services/Email/Mailjet/MailjetEmailServiceExtension.cs b/API/Services/Email/Mailjet/MailjetEmailServiceExtension.cs index a6d1f4e2..31fe0758 100644 --- a/API/Services/Email/Mailjet/MailjetEmailServiceExtension.cs +++ b/API/Services/Email/Mailjet/MailjetEmailServiceExtension.cs @@ -11,8 +11,11 @@ public static WebApplicationBuilder AddMailjetEmailService(this WebApplicationBu { var section = builder.Configuration.GetRequiredSection(MailJetOptions.SectionName); + + // TODO Simplify this builder.Services.Configure(section); builder.Services.AddSingleton, MailJetOptionsValidator>(); + builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); var options = section.Get() ?? throw new NullReferenceException("MailJetOptions is null!"); var basicAuthValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{options.Key}:{options.Secret}")); diff --git a/API/Services/Email/Smtp/SmtpEmailService.cs b/API/Services/Email/Smtp/SmtpEmailService.cs index 3c0a290b..6ae09bc1 100644 --- a/API/Services/Email/Smtp/SmtpEmailService.cs +++ b/API/Services/Email/Smtp/SmtpEmailService.cs @@ -13,8 +13,8 @@ namespace OpenShock.API.Services.Email.Smtp; public sealed class SmtpEmailService : IEmailService { private readonly SmtpServiceTemplates _templates; - private readonly MailboxAddress _sender; private readonly SmtpOptions _options; + private readonly MailboxAddress _sender; private readonly ILogger _logger; private readonly TemplateOptions _templateOptions; @@ -24,19 +24,19 @@ public sealed class SmtpEmailService : IEmailService /// DI Constructor /// /// - /// /// + /// /// public SmtpEmailService( SmtpServiceTemplates templates, - IOptions sender, IOptions options, + MailOptions.MailSenderContact sender, ILogger logger ) { _templates = templates; - _sender = sender.Value.ToMailAddress(); _options = options.Value; + _sender = sender.ToMailAddress(); _logger = logger; // This class is will be registered as a singleton, static members are not needed diff --git a/API/Services/Email/Smtp/SmtpEmailServiceExtension.cs b/API/Services/Email/Smtp/SmtpEmailServiceExtension.cs index db89d09b..d20bb75d 100644 --- a/API/Services/Email/Smtp/SmtpEmailServiceExtension.cs +++ b/API/Services/Email/Smtp/SmtpEmailServiceExtension.cs @@ -9,8 +9,10 @@ public static WebApplicationBuilder AddSmtpEmailService(this WebApplicationBuild { var section = builder.Configuration.GetRequiredSection(SmtpOptions.SectionName); + // TODO Simplify this builder.Services.Configure(section); builder.Services.AddSingleton, SmtpOptionsValidator>(); + builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddSingleton(new SmtpServiceTemplates { diff --git a/API/Services/Turnstile/CloudflareTurnstileService.cs b/API/Services/Turnstile/CloudflareTurnstileService.cs index a90de2e9..e571cf30 100644 --- a/API/Services/Turnstile/CloudflareTurnstileService.cs +++ b/API/Services/Turnstile/CloudflareTurnstileService.cs @@ -16,10 +16,10 @@ public sealed class CloudflareTurnstileService : ICloudflareTurnstileService private readonly IHostEnvironment _environment; private readonly ILogger _logger; - public CloudflareTurnstileService(HttpClient httpClient, IOptions options, IHostEnvironment environment, ILogger logger) + public CloudflareTurnstileService(HttpClient httpClient, TurnstileOptions options, IHostEnvironment environment, ILogger logger) { _httpClient = httpClient; - _options = options.Value; + _options = options; _environment = environment; _logger = logger; } diff --git a/API/Services/Turnstile/CloudflareTurnstileServiceExtensions.cs b/API/Services/Turnstile/CloudflareTurnstileServiceExtensions.cs index c8d9daa5..ad18479f 100644 --- a/API/Services/Turnstile/CloudflareTurnstileServiceExtensions.cs +++ b/API/Services/Turnstile/CloudflareTurnstileServiceExtensions.cs @@ -10,8 +10,10 @@ public static WebApplicationBuilder AddCloudflareTurnstileService(this WebApplic { var section = builder.Configuration.GetRequiredSection(TurnstileOptions.Turnstile); + // TODO Simplify this builder.Services.Configure(section); builder.Services.AddSingleton, TurnstileOptionsValidator>(); + builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services.AddHttpClient(client => { diff --git a/Common/Extensions/ConfigurationExtensions.cs b/Common/Extensions/ConfigurationExtensions.cs index 92fb5435..051c0f6e 100644 --- a/Common/Extensions/ConfigurationExtensions.cs +++ b/Common/Extensions/ConfigurationExtensions.cs @@ -1,26 +1,106 @@ -using Microsoft.Extensions.Options; -using OpenShock.Common.Options; +using OpenShock.Common.Options; +using OpenShock.Common.Utils; +using StackExchange.Redis; namespace OpenShock.Common.Extensions; public static class ConfigurationExtensions { - public static WebApplicationBuilder RegisterCommonOpenShockOptions(this WebApplicationBuilder builder) + public static DatabaseOptions RegisterDatabaseOptions(this WebApplicationBuilder builder) { - if (builder.Environment.IsDevelopment()) + var options = builder.Configuration.GetRequiredSection("OpenShock:DB").Get(); + if (options is null) + throw new InvalidOperationException("Missing or invalid configuration for OpenShock:DB."); + + if (string.IsNullOrEmpty(options.Conn)) throw new InvalidOperationException("Missing or invalid connection string (OpenShock:DB:Conn)."); + + builder.Services.AddSingleton(options); + return options; + } + + private sealed class RedisSection + { + public string? Conn { get; init; } + public string? User { get; init; } + public string? Password { get; init; } + public string? Host { get; init; } + public string? Port { get; init; } + } + + public static ConfigurationOptions RegisterRedisOptions(this WebApplicationBuilder builder) + { + var section = builder.Configuration.GetRequiredSection("OpenShock:Redis").Get(); + if (section is null) + throw new InvalidOperationException("Missing or invalid configuration for OpenShock:Redis."); + + ConfigurationOptions options; + + if (!string.IsNullOrWhiteSpace(section.Conn)) { - Console.WriteLine(builder.Configuration.GetDebugView()); + options = ConfigurationOptions.Parse(section.Conn); } + else + { + if (string.IsNullOrWhiteSpace(section.Host)) + throw new ArgumentException("Redis Host is required (OpenShock:Redis:Host)."); + + if (string.IsNullOrWhiteSpace(section.Password)) + throw new ArgumentException("Redis Password is required (OpenShock:Redis:Password)."); + + // Parse port with sane default + validation + ushort port = 6379; + if (!string.IsNullOrWhiteSpace(section.Port)) + { + if (!ushort.TryParse(section.Port, out port) || port == 0) + throw new InvalidOperationException("Redis Port must be a number between 1 and 65535 (OpenShock:Redis:Port)."); + } + + options = new ConfigurationOptions + { + User = section.User, + Password = section.Password, + Ssl = false, + }; + options.EndPoints.Add(section.Host!, port); + } + + // Sensible defaults (adjust to taste) + options.AbortOnConnectFail = true; + + builder.Services.AddSingleton(options); + return options; + } + + public static MetricsOptions RegisterMetricsOptions(this WebApplicationBuilder builder) + { + var options = builder.Configuration.GetSection("OpenShock:Metrics").Get() ?? new MetricsOptions + { + AllowedNetworks = TrustedProxiesFetcher.PrivateNetworks + }; + + builder.Services.AddSingleton(options); + return options; + } + + public static FrontendOptions RegisterFrontendOptions(this WebApplicationBuilder builder) + { + var section = builder.Configuration.GetRequiredSection("OpenShock:Frontend"); - builder.Services.Configure(builder.Configuration.GetRequiredSection(DatabaseOptions.SectionName)); - builder.Services.AddSingleton, DatabaseOptionsValidator>(); - - builder.Services.Configure(builder.Configuration.GetRequiredSection(RedisOptions.SectionName)); - builder.Services.AddSingleton, RedisOptionsValidator>(); + var options = new FrontendOptions + { + BaseUrl = section.GetValue("BaseUrl") ?? throw new InvalidOperationException("Frontend BaseUrl is required (OpenShock:Frontend:BaseUrl)."), + ShortUrl = section.GetValue("ShortUrl") ?? throw new InvalidOperationException("Frontend ShortUrl is required (OpenShock:Frontend:ShortUrl)."), + CookieDomains = SplitCsv(section["CookieDomain"] ?? throw new InvalidOperationException("Frontend CookieDomain is required (OpenShock:Frontend:CookieDomain).")), + }; - builder.Services.Configure(builder.Configuration.GetSection(MetricsOptions.SectionName)); - builder.Services.AddSingleton, MetricsOptionsValidator>(); + if (options.CookieDomains.Count == 0) throw new InvalidOperationException("At least one cookie domain must be configured (OpenShock:Frontend:CookieDomain)."); - return builder; + builder.Services.AddSingleton(options); + return options; + + static string[] SplitCsv(string csv) + { + return csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } } } diff --git a/Common/OpenShockMiddlewareHelper.cs b/Common/OpenShockMiddlewareHelper.cs index 43c905f7..712de703 100644 --- a/Common/OpenShockMiddlewareHelper.cs +++ b/Common/OpenShockMiddlewareHelper.cs @@ -25,7 +25,7 @@ public static class OpenShockMiddlewareHelper public static async Task UseCommonOpenShockMiddleware(this WebApplication app) { - var metricsOptions = app.Services.GetRequiredService>().Value; + var metricsOptions = app.Services.GetRequiredService(); var metricsAllowedIpNetworks = metricsOptions.AllowedNetworks.Select(x => IPNetwork.Parse(x)).ToArray(); foreach (var proxy in await TrustedProxiesFetcher.GetTrustedNetworksAsync()) diff --git a/Common/OpenShockServiceHelper.cs b/Common/OpenShockServiceHelper.cs index c845791e..9610a851 100644 --- a/Common/OpenShockServiceHelper.cs +++ b/Common/OpenShockServiceHelper.cs @@ -34,53 +34,11 @@ namespace OpenShock.Common; public static class OpenShockServiceHelper { - public static DatabaseOptions GetDatabaseOptions(this ConfigurationManager configuration) - { - var section = configuration.GetRequiredSection(DatabaseOptions.SectionName); - if (!section.Exists()) throw new Exception("TODO"); - - return section.Get() ?? throw new Exception("TODO"); - } - - public static ConfigurationOptions GetRedisConfigurationOptions(this ConfigurationManager configuration) - { - var section = configuration.GetRequiredSection(RedisOptions.SectionName); - if (!section.Exists()) throw new Exception("TODO"); - - var options = section.Get() ?? throw new Exception("TODO"); - - - ConfigurationOptions configurationOptions; - - if (string.IsNullOrWhiteSpace(options.Conn)) - { - configurationOptions = new ConfigurationOptions - { - AbortOnConnectFail = true, - Password = options.Password, - User = options.User, - Ssl = false, - EndPoints = new EndPointCollection - { - { options.Host, options.Port } - } - }; - } - else - { - configurationOptions = ConfigurationOptions.Parse(options.Conn); - } - - configurationOptions.AbortOnConnectFail = true; - - return configurationOptions; - } - public static IServiceCollection AddOpenShockMemDB(this IServiceCollection services, ConfigurationOptions options) { // <---- Redis ----> services.AddSingleton(ConnectionMultiplexer.Connect(options)); - services.AddSingleton(); + services.AddSingleton(serviceProvider => new RedisConnectionProvider(serviceProvider.GetRequiredService())); services.AddSingleton(); return services; diff --git a/Common/Options/DatabaseOptions.cs b/Common/Options/DatabaseOptions.cs index 545b3779..9bd12fd2 100644 --- a/Common/Options/DatabaseOptions.cs +++ b/Common/Options/DatabaseOptions.cs @@ -1,19 +1,8 @@ -using Microsoft.Extensions.Options; -using System.ComponentModel.DataAnnotations; - -namespace OpenShock.Common.Options; +namespace OpenShock.Common.Options; public sealed class DatabaseOptions { - public const string SectionName = "OpenShock:DB"; - - [Required(AllowEmptyStrings = false)] public required string Conn { get; init; } public bool SkipMigration { get; init; } = false; public bool Debug { get; init; } = false; -} - -[OptionsValidator] -public partial class DatabaseOptionsValidator : IValidateOptions -{ } \ No newline at end of file diff --git a/Common/Options/FrontendOptions.cs b/Common/Options/FrontendOptions.cs index bff96817..6a41bac4 100644 --- a/Common/Options/FrontendOptions.cs +++ b/Common/Options/FrontendOptions.cs @@ -1,23 +1,8 @@ -using Microsoft.Extensions.Options; -using System.ComponentModel.DataAnnotations; - -namespace OpenShock.Common.Options; +namespace OpenShock.Common.Options; public sealed class FrontendOptions { - public const string SectionName = "OpenShock:Frontend"; - - [Required] public required Uri BaseUrl { get; init; } - - [Required] public required Uri ShortUrl { get; init; } - - [Required(AllowEmptyStrings = false)] - public required string CookieDomain { get; init; } -} - -[OptionsValidator] -public partial class FrontendOptionsValidator : IValidateOptions -{ + public required IReadOnlyCollection CookieDomains { get; init; } } \ No newline at end of file diff --git a/Common/Options/MetricsOptions.cs b/Common/Options/MetricsOptions.cs index d32314cf..6d4d672c 100644 --- a/Common/Options/MetricsOptions.cs +++ b/Common/Options/MetricsOptions.cs @@ -1,21 +1,6 @@ -using Microsoft.Extensions.Options; -using OpenShock.Common.Utils; - -namespace OpenShock.Common.Options; +namespace OpenShock.Common.Options; public sealed class MetricsOptions { - public const string SectionName = "OpenShock:Metrics"; - - public IReadOnlyCollection AllowedNetworks { get; init; } = TrustedProxiesFetcher.PrivateNetworks; -} - -public class MetricsOptionsValidator : IValidateOptions -{ - public ValidateOptionsResult Validate(string? name, MetricsOptions options) - { - var builder = new ValidateOptionsResultBuilder(); - - return builder.Build(); - } + public required IReadOnlyCollection AllowedNetworks { get; init; } } \ No newline at end of file diff --git a/Common/Options/RedisOptions.cs b/Common/Options/RedisOptions.cs deleted file mode 100644 index 42badfb9..00000000 --- a/Common/Options/RedisOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.Extensions.Options; - -namespace OpenShock.Common.Options; - -public sealed class RedisOptions -{ - public const string SectionName = "OpenShock:Redis"; - - public required string Conn { get; set; } - public required string Host { get; init; } = string.Empty; - public string User { get; init; } = string.Empty; - public string Password { get; init; } = string.Empty; - public ushort Port { get; init; } = 6379; -} - -public sealed class RedisOptionsValidator : IValidateOptions -{ - public ValidateOptionsResult Validate(string? name, RedisOptions options) - { - ValidateOptionsResultBuilder builder = new ValidateOptionsResultBuilder(); - - if (string.IsNullOrEmpty(options.Conn)) - { - if (string.IsNullOrEmpty(options.Host)) builder.AddError("Host field is required if no connectionstring is specified", nameof(options.Host)); - if (string.IsNullOrEmpty(options.User)) builder.AddError("User field is required if no connectionstring is specified", nameof(options.Host)); - if (!string.IsNullOrEmpty(options.Password)) builder.AddError("Password field is required if no connectionstring is specified", nameof(options.Host)); - } - - return builder.Build(); - } -} \ No newline at end of file diff --git a/Cron/Program.cs b/Cron/Program.cs index d74f4f97..43b5cbb2 100644 --- a/Cron/Program.cs +++ b/Cron/Program.cs @@ -8,18 +8,17 @@ var builder = OpenShockApplication.CreateDefaultBuilder(args); -builder.RegisterCommonOpenShockOptions(); +var redisOptions = builder.RegisterRedisOptions(); +var databaseOptions = builder.RegisterDatabaseOptions(); +builder.RegisterMetricsOptions(); -var databaseConfig = builder.Configuration.GetDatabaseOptions(); -var redisConfig = builder.Configuration.GetRedisConfigurationOptions(); - -builder.Services.AddOpenShockMemDB(redisConfig); -builder.Services.AddOpenShockDB(databaseConfig); +builder.Services.AddOpenShockMemDB(redisOptions); +builder.Services.AddOpenShockDB(databaseOptions); builder.Services.AddOpenShockServices(); builder.Services.AddHangfire(hangfire => hangfire.UsePostgreSqlStorage(c => - c.UseNpgsqlConnection(databaseConfig.Conn))); + c.UseNpgsqlConnection(databaseOptions.Conn))); builder.Services.AddHangfireServer(); builder.AddSwaggerExt(); diff --git a/LiveControlGateway/Controllers/HubControllerBase.cs b/LiveControlGateway/Controllers/HubControllerBase.cs index 5b7b4c4c..192e8304 100644 --- a/LiveControlGateway/Controllers/HubControllerBase.cs +++ b/LiveControlGateway/Controllers/HubControllerBase.cs @@ -90,13 +90,13 @@ protected HubControllerBase( ISerializer outgoingSerializer, HubLifetimeManager hubLifetimeManager, IServiceProvider serviceProvider, - IOptions options, + LcgOptions options, ILogger> logger ) : base(logger, incomingSerializer, outgoingSerializer) { _hubLifetimeManager = hubLifetimeManager; ServiceProvider = serviceProvider; - _options = options.Value; + _options = options; _keepAliveTimeoutTimer.Elapsed += async (_, _) => { try diff --git a/LiveControlGateway/Controllers/HubV1Controller.cs b/LiveControlGateway/Controllers/HubV1Controller.cs index 505c1a01..55ab9e14 100644 --- a/LiveControlGateway/Controllers/HubV1Controller.cs +++ b/LiveControlGateway/Controllers/HubV1Controller.cs @@ -38,7 +38,7 @@ public HubV1Controller( HubLifetimeManager hubLifetimeManager, IHubContext userHubContext, IServiceProvider serviceProvider, - IOptions options, + LcgOptions options, ILogger logger ) : base(HubToGatewayMessage.Serializer, GatewayToHubMessage.Serializer, hubLifetimeManager, serviceProvider, options, logger) diff --git a/LiveControlGateway/Controllers/HubV2Controller.cs b/LiveControlGateway/Controllers/HubV2Controller.cs index e5535287..637fca9a 100644 --- a/LiveControlGateway/Controllers/HubV2Controller.cs +++ b/LiveControlGateway/Controllers/HubV2Controller.cs @@ -43,7 +43,7 @@ public HubV2Controller( HubLifetimeManager hubLifetimeManager, IHubContext userHubContext, IServiceProvider serviceProvider, - IOptions options, + LcgOptions options, ILogger logger ) : base(HubToGatewayMessage.Serializer, GatewayToHubMessage.Serializer, hubLifetimeManager, serviceProvider, options, logger) diff --git a/LiveControlGateway/Controllers/InstanceDetailsController.cs b/LiveControlGateway/Controllers/InstanceDetailsController.cs index 0f779d5b..d055a63f 100644 --- a/LiveControlGateway/Controllers/InstanceDetailsController.cs +++ b/LiveControlGateway/Controllers/InstanceDetailsController.cs @@ -28,7 +28,7 @@ public sealed class InstanceDetailsController : OpenShockControllerBase [HttpGet] [ProducesResponseType(StatusCodes.Status200OK, MediaTypeNames.Application.Json)] [MapToApiVersion("1")] - public InstanceDetailsResponse GetNodeInfo([FromServices] IOptions options) + public InstanceDetailsResponse GetNodeInfo([FromServices] LcgOptions options) { return new InstanceDetailsResponse { @@ -36,8 +36,8 @@ public InstanceDetailsResponse GetNodeInfo([FromServices] IOptions o Version = AssemblyVersion, Commit = GitHashAttribute.FullHash, CurrentTime = DateTimeOffset.UtcNow, - Fqdn = options.Value.Fqdn, - CountryCode = options.Value.CountryCode + Fqdn = options.Fqdn, + CountryCode = options.CountryCode }; } diff --git a/LiveControlGateway/LcgKeepAlive.cs b/LiveControlGateway/LcgKeepAlive.cs index 25b7f126..63ae388b 100644 --- a/LiveControlGateway/LcgKeepAlive.cs +++ b/LiveControlGateway/LcgKeepAlive.cs @@ -26,11 +26,11 @@ public sealed class LcgKeepAlive : IHostedService /// /// /// - public LcgKeepAlive(IRedisConnectionProvider redisConnectionProvider, IWebHostEnvironment env, IOptions options, ILogger logger) + public LcgKeepAlive(IRedisConnectionProvider redisConnectionProvider, IWebHostEnvironment env, LcgOptions options, ILogger logger) { _redisConnectionProvider = redisConnectionProvider; _env = env; - _options = options.Value; + _options = options; _logger = logger; } diff --git a/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs index 389e083b..f01d350f 100644 --- a/LiveControlGateway/Program.cs +++ b/LiveControlGateway/Program.cs @@ -12,19 +12,20 @@ var builder = OpenShockApplication.CreateDefaultBuilder(args); -builder.RegisterCommonOpenShockOptions(); +var redisOptions = builder.RegisterRedisOptions(); +var databaseOptions = builder.RegisterDatabaseOptions(); +builder.RegisterMetricsOptions(); +// TODO Simplify this builder.Services.Configure(builder.Configuration.GetRequiredSection(LcgOptions.SectionName)); builder.Services.AddSingleton, LcgOptionsValidator>(); - -var databaseConfig = builder.Configuration.GetDatabaseOptions(); -var redisConfig = builder.Configuration.GetRedisConfigurationOptions(); +builder.Services.AddSingleton(sp => sp.GetRequiredService>().Value); builder.Services - .AddOpenShockMemDB(redisConfig) - .AddOpenShockDB(databaseConfig) + .AddOpenShockMemDB(redisOptions) + .AddOpenShockDB(databaseOptions) .AddOpenShockServices() - .AddOpenShockSignalR(redisConfig); + .AddOpenShockSignalR(redisOptions); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/SeedE2E/Program.cs b/SeedE2E/Program.cs index e1fe30a6..bbe599e1 100644 --- a/SeedE2E/Program.cs +++ b/SeedE2E/Program.cs @@ -16,19 +16,15 @@ Console.WriteLine(builder.Configuration.GetDebugView()); } -builder.RegisterCommonOpenShockOptions(); +var databaseOptions = builder.RegisterDatabaseOptions(); +builder.RegisterFrontendOptions(); -builder.Services.Configure(builder.Configuration.GetRequiredSection(FrontendOptions.SectionName)); -builder.Services.AddSingleton, FrontendOptionsValidator>(); - -var databaseConfig = builder.Configuration.GetDatabaseOptions(); - -builder.Services.AddOpenShockDB(databaseConfig); +builder.Services.AddOpenShockDB(databaseOptions); builder.Services.AddOpenShockServices(); var app = builder.Build(); -await app.ApplyPendingOpenShockMigrations(databaseConfig); +await app.ApplyPendingOpenShockMigrations(databaseOptions); // --- SEED ALL THE DATABASE TABLES --- using (var scope = app.Services.CreateScope()) @@ -36,8 +32,8 @@ using var loggerFactory = scope.ServiceProvider.GetRequiredService(); var logger = loggerFactory.CreateLogger("OpenShock.SeedE2E"); - using var db = scope.ServiceProvider.GetRequiredService(); - using var transaction = await db.Database.BeginTransactionAsync(); + await using var db = scope.ServiceProvider.GetRequiredService(); + await using var transaction = await db.Database.BeginTransactionAsync(); // Core entities await UserSeeder.SeedAsync(db, logger);