diff --git a/API/API.csproj b/API/API.csproj
index 5e91d68a..57b7be35 100644
--- a/API/API.csproj
+++ b/API/API.csproj
@@ -36,7 +36,6 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
diff --git a/API/Program.cs b/API/Program.cs
index bfcfa02b..1ed81d91 100644
--- a/API/Program.cs
+++ b/API/Program.cs
@@ -1,49 +1,123 @@
+using Microsoft.AspNetCore.Http.Connections;
+using Microsoft.EntityFrameworkCore;
using OpenShock.API;
+using OpenShock.API.Realtime;
+using OpenShock.API.Services;
+using OpenShock.API.Services.Account;
+using OpenShock.API.Services.Email.Mailjet;
+using OpenShock.API.Services.Email.Smtp;
+using OpenShock.Common;
+using OpenShock.Common.Extensions;
+using OpenShock.Common.Hubs;
+using OpenShock.Common.JsonSerialization;
+using OpenShock.Common.OpenShockDb;
+using OpenShock.Common.Services.Device;
+using OpenShock.Common.Services.LCGNodeProvisioner;
+using OpenShock.Common.Services.Ota;
+using OpenShock.Common.Services.Turnstile;
+using OpenShock.Common.Utils;
+using Scalar.AspNetCore;
using Serilog;
-HostBuilder builder = new();
-builder.UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureHostConfiguration(config =>
- {
- config.AddEnvironmentVariables(prefix: "DOTNET_");
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .ConfigureAppConfiguration((context, config) =>
- {
- config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
- .AddJsonFile("appsettings.Custom.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true,
- reloadOnChange: false);
-
- config.AddUserSecrets(typeof(Program).Assembly);
- config.AddEnvironmentVariables();
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .UseDefaultServiceProvider((context, options) =>
+var builder = OpenShockApplication.CreateDefaultBuilder(args, options =>
+{
+ options.ListenAnyIP(80);
+#if DEBUG
+ options.ListenAnyIP(443, options => options.UseHttps());
+#endif
+});
+
+var config = builder.GetAndRegisterOpenShockConfig();
+var commonServices = builder.Services.AddOpenShockServices(config);
+
+builder.Services.AddSignalR()
+ .AddOpenShockStackExchangeRedis(options => { options.Configuration = commonServices.RedisConfig; })
+ .AddJsonProtocol(options =>
{
- var isDevelopment = context.HostingEnvironment.IsDevelopment();
- options.ValidateScopes = isDevelopment;
- options.ValidateOnBuild = isDevelopment;
- })
- .UseSerilog((context, _, config) => { config.ReadFrom.Configuration(context.Configuration); })
- .ConfigureWebHostDefaults(webBuilder =>
+ options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
+ options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter());
+ });
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+builder.Services.AddSwaggerExt("OpenShock.API");
+
+builder.Services.AddSingleton();
+
+builder.Services.AddSingleton(x =>
+{
+ return new CloudflareTurnstileOptions
{
- webBuilder.UseKestrel();
- webBuilder.ConfigureKestrel(serverOptions =>
+ SecretKey = config.Turnstile.SecretKey ?? string.Empty,
+ SiteKey = config.Turnstile.SiteKey ?? string.Empty
+ };
+});
+builder.Services.AddHttpClient();
+
+// ----------------- MAIL SETUP -----------------
+var emailConfig = config.Mail;
+switch (emailConfig.Type)
+{
+ case ApiConfig.MailConfig.MailType.Mailjet:
+ if (emailConfig.Mailjet == null)
+ throw new Exception("Mailjet config is null but mailjet is selected as mail type");
+ builder.Services.AddMailjetEmailService(emailConfig.Mailjet, emailConfig.Sender);
+ break;
+ case ApiConfig.MailConfig.MailType.Smtp:
+ if (emailConfig.Smtp == null)
+ throw new Exception("SMTP config is null but SMTP is selected as mail type");
+ builder.Services.AddSmtpEmailService(emailConfig.Smtp, emailConfig.Sender, new SmtpServiceTemplates
{
- serverOptions.ListenAnyIP(80);
-#if DEBUG
- serverOptions.ListenAnyIP(443, options => { options.UseHttps(); });
-#endif
- serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(3000);
+ PasswordReset = SmtpTemplate.ParseFromFileThrow("SmtpTemplates/PasswordReset.liquid").Result,
+ EmailVerification = SmtpTemplate.ParseFromFileThrow("SmtpTemplates/EmailVerification.liquid").Result
});
- webBuilder.UseStartup();
- });
-try
+ break;
+ default:
+ throw new Exception("Unknown mail type");
+}
+
+builder.Services.ConfigureOptions();
+//services.AddHealthChecks().AddCheck("database");
+
+builder.Services.AddHostedService();
+
+var app = builder.Build();
+
+app.UseCommonOpenShockMiddleware();
+
+if (!config.Db.SkipMigration)
{
- await builder.Build().RunAsync();
+ Log.Information("Running database migrations...");
+ using var scope = app.Services.CreateScope();
+ var openShockContext = scope.ServiceProvider.GetRequiredService();
+ var pendingMigrations = openShockContext.Database.GetPendingMigrations().ToList();
+
+ if (pendingMigrations.Count > 0)
+ {
+ Log.Information("Found pending migrations, applying [{@Migrations}]", pendingMigrations);
+ openShockContext.Database.Migrate();
+ Log.Information("Applied database migrations... proceeding with startup");
+ }
+ else
+ {
+ Log.Information("No pending migrations found, proceeding with startup");
+ }
}
-catch (Exception e)
+else
{
- Console.WriteLine(e);
-}
\ No newline at end of file
+ Log.Warning("Skipping possible database migrations...");
+}
+
+app.UseSwaggerExt();
+
+app.MapControllers();
+
+app.MapHub("/1/hubs/user", options => options.Transports = HttpTransportType.WebSockets);
+app.MapHub("/1/hubs/share/link/{id:guid}", options => options.Transports = HttpTransportType.WebSockets);
+
+app.MapScalarApiReference(options => options.OpenApiRoutePattern = "/swagger/{documentName}/swagger.json");
+
+app.Run();
diff --git a/API/Properties/launchSettings.json b/API/Properties/launchSettings.json
index c027337d..abfc1566 100644
--- a/API/Properties/launchSettings.json
+++ b/API/Properties/launchSettings.json
@@ -1,18 +1,10 @@
{
"profiles": {
- "ShockLink": {
+ "API": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DB": "Host=docker-node;Port=1337;Database=root;Username=root;Password=root;Search Path=ShockLink",
- "REDIS_HOST":"docker-node",
- "REDIS_PASSWORD": "",
- "CF_ACC_ID": "",
- "CF_IMG_KEY": "",
- "CF_IMG_URL": "",
- "MAILJET_KEY": "",
- "MAILJET_SECRET": ""
+ "ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
diff --git a/API/Startup.cs b/API/Startup.cs
deleted file mode 100644
index ce6df6d8..00000000
--- a/API/Startup.cs
+++ /dev/null
@@ -1,216 +0,0 @@
-using System.Text;
-using Asp.Versioning.ApiExplorer;
-using Microsoft.AspNetCore.Http.Connections;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.OpenApi.Models;
-using OpenShock.API.Realtime;
-using OpenShock.API.Services;
-using OpenShock.API.Services.Account;
-using OpenShock.API.Services.Email.Mailjet;
-using OpenShock.API.Services.Email.Smtp;
-using OpenShock.Common;
-using OpenShock.Common.Constants;
-using OpenShock.Common.DataAnnotations;
-using OpenShock.Common.Hubs;
-using OpenShock.Common.JsonSerialization;
-using OpenShock.Common.Models;
-using OpenShock.Common.OpenShockDb;
-using OpenShock.Common.Redis;
-using OpenShock.Common.Services.Device;
-using OpenShock.Common.Services.LCGNodeProvisioner;
-using OpenShock.Common.Services.Ota;
-using OpenShock.Common.Services.Session;
-using OpenShock.Common.Services.Turnstile;
-using OpenShock.Common.Utils;
-using Redis.OM;
-using Redis.OM.Contracts;
-using Scalar.AspNetCore;
-using Semver;
-using Serilog;
-
-namespace OpenShock.API;
-
-public sealed class Startup
-{
-
- private readonly ApiConfig _apiConfig;
-
- public Startup(IConfiguration configuration)
- {
- _apiConfig = configuration.GetChildren()
- .FirstOrDefault(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))?
- .Get() ??
- throw new Exception("Couldn't bind config, check config file");
-
- var startupLogger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
-
- MiniValidation.MiniValidator.TryValidate(_apiConfig, true, true, out var errors);
- if (errors.Count > 0)
- {
- var sb = new StringBuilder();
-
- foreach (var error in errors)
- {
- sb.AppendLine($"Error on field [{error.Key}] reason: {string.Join(", ", error.Value)}");
- }
-
- startupLogger.Error(
- "Error validating config, please fix your configuration / environment variables\nFound the following errors:\n{Errors}",
- sb.ToString());
- Environment.Exit(-10);
- }
- }
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddSingleton(_apiConfig);
-
- var commonServices = services.AddOpenShockServices(_apiConfig);
-
- services.AddSingleton();
-
- services.AddSingleton(x =>
- {
- var config = x.GetRequiredService();
- return new CloudflareTurnstileOptions
- {
- SecretKey = config.Turnstile.SecretKey ?? string.Empty,
- SiteKey = config.Turnstile.SiteKey ?? string.Empty
- };
- });
- services.AddHttpClient();
-
- // ----------------- MAIL SETUP -----------------
- var emailConfig = _apiConfig.Mail;
- switch (emailConfig.Type)
- {
- case ApiConfig.MailConfig.MailType.Mailjet:
- if (emailConfig.Mailjet == null)
- throw new Exception("Mailjet config is null but mailjet is selected as mail type");
- services.AddMailjetEmailService(emailConfig.Mailjet, emailConfig.Sender);
- break;
- case ApiConfig.MailConfig.MailType.Smtp:
- if (emailConfig.Smtp == null)
- throw new Exception("SMTP config is null but SMTP is selected as mail type");
- services.AddSmtpEmailService(emailConfig.Smtp, emailConfig.Sender, new SmtpServiceTemplates
- {
- PasswordReset = SmtpTemplate.ParseFromFileThrow("SmtpTemplates/PasswordReset.liquid").Result,
- EmailVerification = SmtpTemplate.ParseFromFileThrow("SmtpTemplates/EmailVerification.liquid").Result
- });
- break;
- default:
- throw new Exception("Unknown mail type");
- }
-
- services.AddSignalR()
- .AddOpenShockStackExchangeRedis(options => { options.Configuration = commonServices.RedisConfig; })
- .AddJsonProtocol(options =>
- {
- options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
- options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter());
- });
-
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
-
- services.AddSwaggerGen(options =>
- {
- options.CustomOperationIds(e =>
- $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.AttributeRouteInfo?.Name ?? e.ActionDescriptor.RouteValues["action"]}");
- options.SchemaFilter();
- options.ParameterFilter();
- options.OperationFilter();
- options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "OpenShock.API.xml"), true);
- options.AddSecurityDefinition(AuthConstants.AuthTokenHeaderName, new OpenApiSecurityScheme
- {
- Name = AuthConstants.AuthTokenHeaderName,
- Type = SecuritySchemeType.ApiKey,
- Scheme = "ApiKeyAuth",
- In = ParameterLocation.Header,
- Description = "API Token Authorization header."
- });
- options.AddSecurityRequirement(new OpenApiSecurityRequirement
- {
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = AuthConstants.AuthTokenHeaderName
- }
- },
- Array.Empty()
- }
- });
- options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" });
- options.AddServer(new OpenApiServer { Url = "https://staging-api.openshock.app" });
-#if DEBUG
- options.AddServer(new OpenApiServer { Url = "https://localhost" });
-#endif
- options.SwaggerDoc("v1", new OpenApiInfo { Title = "OpenShock", Version = "1" });
- options.SwaggerDoc("v2", new OpenApiInfo { Title = "OpenShock", Version = "2" });
- options.MapType(() => OpenApiSchemas.SemVerSchema);
- options.MapType(() => OpenApiSchemas.PauseReasonEnumSchema);
-
- // Avoid nullable strings everywhere
- options.SupportNonNullableReferenceTypes();
- }
- );
-
- services.ConfigureOptions();
- //services.AddHealthChecks().AddCheck("database");
-
- services.AddHostedService();
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory,
- ILogger logger)
- {
- ApplicationLogging.LoggerFactory = loggerFactory;
-
- app.UseCommonOpenShockMiddleware();
-
- if (!_apiConfig.Db.SkipMigration)
- {
- logger.LogInformation("Running database migrations...");
- using var scope = app.ApplicationServices.CreateScope();
- var openShockContext = scope.ServiceProvider.GetRequiredService();
- var pendingMigrations = openShockContext.Database.GetPendingMigrations().ToList();
-
- if (pendingMigrations.Count > 0)
- {
- logger.LogInformation("Found pending migrations, applying [{@Migrations}]", pendingMigrations);
- openShockContext.Database.Migrate();
- logger.LogInformation("Applied database migrations... proceeding with startup");
- }
- else logger.LogInformation("No pending migrations found, proceeding with startup");
- }
- else logger.LogWarning("Skipping possible database migrations...");
-
- app.UseSwagger();
- var provider = app.ApplicationServices.GetRequiredService();
- app.UseSwaggerUI(c =>
- {
- foreach (var description in provider.ApiVersionDescriptions)
- c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
- description.GroupName.ToUpperInvariant());
- });
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
- endpoints.MapHub("/1/hubs/user",
- options => { options.Transports = HttpTransportType.WebSockets; });
- endpoints.MapHub("/1/hubs/share/link/{id}",
- options => { options.Transports = HttpTransportType.WebSockets; });
-
- endpoints.MapScalarApiReference(options =>
- {
- options.OpenApiRoutePattern = "/swagger/{documentName}/swagger.json";
- });
- });
- }
-}
\ No newline at end of file
diff --git a/Common/ApplicationLogging.cs b/Common/ApplicationLogging.cs
deleted file mode 100644
index 2fd5be32..00000000
--- a/Common/ApplicationLogging.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.Extensions.Logging;
-
-namespace OpenShock.Common;
-
-public static class ApplicationLogging
-{
- public static ILoggerFactory LoggerFactory { get; set; } = null!;
- public static ILogger CreateLogger() => LoggerFactory.CreateLogger();
- public static ILogger CreateLogger(Type type) => LoggerFactory.CreateLogger(type);
- public static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger(categoryName);
-}
\ No newline at end of file
diff --git a/Common/Common.csproj b/Common/Common.csproj
index 6cefdf4c..1c1a5822 100644
--- a/Common/Common.csproj
+++ b/Common/Common.csproj
@@ -14,7 +14,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -29,7 +29,8 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
@@ -40,12 +41,12 @@
-
+
-
+
-
-
+
+
diff --git a/Common/ExceptionHandle/ExceptionHandler.cs b/Common/ExceptionHandle/ExceptionHandler.cs
index bb6679d9..6c7b57c6 100644
--- a/Common/ExceptionHandle/ExceptionHandler.cs
+++ b/Common/ExceptionHandle/ExceptionHandler.cs
@@ -1,21 +1,18 @@
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
-using Microsoft.AspNetCore.Http.Json;
-using Microsoft.Extensions.Options;
using OpenShock.Common.Errors;
namespace OpenShock.Common.ExceptionHandle;
public sealed class OpenShockExceptionHandler : IExceptionHandler
{
- private static readonly ILogger Logger = ApplicationLogging.CreateLogger(typeof(OpenShockExceptionHandler));
- private static readonly ILogger LoggerRequestInfo = ApplicationLogging.CreateLogger("RequestInfo");
-
private readonly IProblemDetailsService _problemDetailsService;
-
- public OpenShockExceptionHandler(IProblemDetailsService problemDetailsService)
+ private readonly ILogger _logger;
+
+ public OpenShockExceptionHandler(IProblemDetailsService problemDetailsService, ILoggerFactory loggerFactory)
{
_problemDetailsService = problemDetailsService;
+ _logger = loggerFactory.CreateLogger("RequestInfo");
}
public async ValueTask TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
@@ -34,7 +31,7 @@ public async ValueTask TryHandleAsync(HttpContext context, Exception excep
});
}
- private static async Task PrintRequestInfo(HttpContext context)
+ private async Task PrintRequestInfo(HttpContext context)
{
// Rewind our body reader, so we can read it again.
context.Request.Body.Seek(0, SeekOrigin.Begin);
@@ -57,6 +54,6 @@ private static async Task PrintRequestInfo(HttpContext context)
};
// Finally log this object on Information level.
- LoggerRequestInfo.LogInformation("{@RequestInfo}", requestInfo);
+ _logger.LogInformation("{@RequestInfo}", requestInfo);
}
}
\ No newline at end of file
diff --git a/Common/Extensions/ConfigurationExtensions.cs b/Common/Extensions/ConfigurationExtensions.cs
new file mode 100644
index 00000000..90509578
--- /dev/null
+++ b/Common/Extensions/ConfigurationExtensions.cs
@@ -0,0 +1,45 @@
+using OpenShock.Common.Config;
+using Serilog;
+using System.Text;
+using System.Text.Json;
+
+namespace OpenShock.Common.Extensions;
+
+public static class ConfigurationExtensions
+{
+ public static T GetAndRegisterOpenShockConfig(this WebApplicationBuilder builder) where T : BaseConfig
+ {
+#if DEBUG
+ Console.WriteLine(builder.Configuration.GetDebugView());
+#endif
+
+ var config = builder.Configuration
+ .GetChildren()
+ .First(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))
+ .Get() ?? throw new Exception("Couldn't bind config, check config file");
+
+ MiniValidation.MiniValidator.TryValidate(config, true, true, out var errors);
+ if (errors.Count > 0)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("Error validating config, please fix your configuration / environment variables");
+ sb.AppendLine("Found the following errors:");
+ foreach (var error in errors)
+ {
+ sb.AppendLine($"Error on field [{error.Key}] reason: {string.Join(", ", error.Value)}");
+ }
+
+ Log.Error(sb.ToString());
+ Environment.Exit(-10);
+ }
+
+#if DEBUG
+ Console.WriteLine(JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }));
+#endif
+
+ builder.Services.AddSingleton(config);
+
+ return config;
+ }
+}
diff --git a/Common/Extensions/SwaggerGenExtensions.cs b/Common/Extensions/SwaggerGenExtensions.cs
new file mode 100644
index 00000000..5f0c3d21
--- /dev/null
+++ b/Common/Extensions/SwaggerGenExtensions.cs
@@ -0,0 +1,74 @@
+using Microsoft.OpenApi.Models;
+using OpenShock.Common.Constants;
+using OpenShock.Common.DataAnnotations;
+using OpenShock.Common.Models;
+using Semver;
+using Asp.Versioning.ApiExplorer;
+
+namespace OpenShock.Common.Extensions;
+
+public static class SwaggerGenExtensions
+{
+ public static IServiceCollection AddSwaggerExt(this IServiceCollection services, string assemblyName)
+ {
+ return services.AddSwaggerGen(options =>
+ {
+ options.CustomOperationIds(e =>
+ $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.AttributeRouteInfo?.Name ?? e.ActionDescriptor.RouteValues["action"]}");
+ options.SchemaFilter();
+ options.ParameterFilter();
+ options.OperationFilter();
+ options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, assemblyName + ".xml"), true);
+ options.AddSecurityDefinition(AuthConstants.AuthTokenHeaderName, new OpenApiSecurityScheme
+ {
+ Name = AuthConstants.AuthTokenHeaderName,
+ Type = SecuritySchemeType.ApiKey,
+ Scheme = "ApiKeyAuth",
+ In = ParameterLocation.Header,
+ Description = "API Token Authorization header."
+ });
+ options.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = AuthConstants.AuthTokenHeaderName
+ }
+ },
+ Array.Empty()
+ }
+ });
+ options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" });
+ options.AddServer(new OpenApiServer { Url = "https://staging-api.openshock.app" });
+#if DEBUG
+ options.AddServer(new OpenApiServer { Url = "https://localhost" });
+#endif
+ options.SwaggerDoc("v1", new OpenApiInfo { Title = "OpenShock", Version = "1" });
+ options.SwaggerDoc("v2", new OpenApiInfo { Title = "OpenShock", Version = "2" });
+ options.MapType(() => OpenApiSchemas.SemVerSchema);
+ options.MapType(() => OpenApiSchemas.PauseReasonEnumSchema);
+
+ // Avoid nullable strings everywhere
+ options.SupportNonNullableReferenceTypes();
+ });
+ }
+
+ public static IApplicationBuilder UseSwaggerExt(this WebApplication app)
+ {
+ var provider = app.Services.GetRequiredService();
+ var groupNames = provider.ApiVersionDescriptions.Select(d => d.GroupName).ToList();
+
+ return app
+ .UseSwagger()
+ .UseSwaggerUI(c =>
+ {
+ foreach (var groupName in groupNames)
+ {
+ c.SwaggerEndpoint($"/swagger/{groupName}/swagger.json", groupName.ToUpperInvariant());
+ }
+ });
+ }
+}
diff --git a/Common/OpenShockApplication.cs b/Common/OpenShockApplication.cs
new file mode 100644
index 00000000..523fc940
--- /dev/null
+++ b/Common/OpenShockApplication.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Serilog;
+
+namespace OpenShock.Common;
+
+public static class OpenShockApplication
+{
+ public static WebApplicationBuilder CreateDefaultBuilder(string[] args, Action configurePorts) where TProgram : class
+ {
+ var builder = WebApplication.CreateSlimBuilder(args);
+
+ builder.Configuration.Sources.Clear();
+ builder.Configuration
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false)
+ .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: false)
+ .AddJsonFile("appsettings.Custom.json", optional: true, reloadOnChange: false)
+ .AddEnvironmentVariables()
+ .AddUserSecrets(true)
+ .AddCommandLine(args);
+
+ var isDevelopment = builder.Environment.IsDevelopment();
+ builder.Host.UseDefaultServiceProvider((_, options) =>
+ {
+ options.ValidateScopes = isDevelopment;
+ options.ValidateOnBuild = isDevelopment;
+ });
+
+ // Since we use slim builders, this allows for HTTPS during local development
+ if (isDevelopment) builder.WebHost.UseKestrelHttpsConfiguration();
+
+ builder.WebHost.ConfigureKestrel(serverOptions =>
+ {
+ configurePorts(serverOptions);
+ serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(3000);
+ });
+
+ builder.Host.UseSerilog((context, _, config) => config.ReadFrom.Configuration(context.Configuration));
+
+ return builder;
+ }
+}
diff --git a/Common/Utils/LucTask.cs b/Common/Utils/LucTask.cs
index 44cec6f2..02a93019 100644
--- a/Common/Utils/LucTask.cs
+++ b/Common/Utils/LucTask.cs
@@ -1,12 +1,10 @@
-using System.Runtime.CompilerServices;
-using Microsoft.Extensions.Logging;
+using Serilog;
+using System.Runtime.CompilerServices;
namespace OpenShock.Common.Utils;
public static class LucTask
{
- private static readonly ILogger Logger = ApplicationLogging.CreateLogger(typeof(LucTask));
-
public static Task Run(Func function, [CallerFilePath] string file = "",
[CallerMemberName] string member = "", [CallerLineNumber] int line = -1) => Task.Run(function).ContinueWith(
t =>
@@ -14,9 +12,9 @@ public static Task Run(Func function, [CallerFilePath] string file = "",
if (!t.IsFaulted) return;
var index = file.LastIndexOf('\\');
if (index == -1) index = file.LastIndexOf('/');
- Logger.LogError(t.Exception,
+ Log.Error(t.Exception,
"Error during task execution. {File}::{Member}:{Line} - Stack: {Stack}",
- file.Substring(index + 1, file.Length - index - 1), member, line, t.Exception?.StackTrace);
+ file[(index + 1)..], member, line, t.Exception?.StackTrace);
}, TaskContinuationOptions.OnlyOnFaulted);
@@ -27,9 +25,9 @@ public static Task Run(Task? function, [CallerFilePath] string file = "",
if (!t.IsFaulted) return;
var index = file.LastIndexOf('\\');
if (index == -1) index = file.LastIndexOf('/');
- Logger.LogError(t.Exception,
+ Log.Error(t.Exception,
"Error during task execution. {File}::{Member}:{Line} - Stack: {Stack}",
- file.Substring(index + 1, file.Length - index - 1), member, line, t.Exception?.StackTrace);
+ file[(index + 1)..], member, line, t.Exception?.StackTrace);
}, TaskContinuationOptions.OnlyOnFaulted);
}
\ No newline at end of file
diff --git a/Cron/Program.cs b/Cron/Program.cs
index 1dfe7397..a353f2e9 100644
--- a/Cron/Program.cs
+++ b/Cron/Program.cs
@@ -1,60 +1,43 @@
using Hangfire;
+using Hangfire.PostgreSql;
+using OpenShock.Common;
+using OpenShock.Common.Extensions;
using OpenShock.Cron;
-using OpenShock.Cron.Jobs;
using OpenShock.Cron.Utils;
-using Serilog;
-
-HostBuilder builder = new();
-builder.UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureHostConfiguration(config =>
- {
- config.AddEnvironmentVariables(prefix: "DOTNET_");
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .ConfigureAppConfiguration((context, config) =>
- {
- config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
- .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true,
- reloadOnChange: false);
-
- config.AddUserSecrets(typeof(Program).Assembly);
- config.AddEnvironmentVariables();
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .UseDefaultServiceProvider((context, options) =>
- {
- var isDevelopment = context.HostingEnvironment.IsDevelopment();
- options.ValidateScopes = isDevelopment;
- options.ValidateOnBuild = isDevelopment;
- })
- .UseSerilog((context, _, config) => { config.ReadFrom.Configuration(context.Configuration); })
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseKestrel();
- webBuilder.ConfigureKestrel(serverOptions =>
- {
- serverOptions.ListenAnyIP(780);
+
+var builder = OpenShockApplication.CreateDefaultBuilder(args, options =>
+{
+ options.ListenAnyIP(780);
#if DEBUG
- serverOptions.ListenAnyIP(7443, options => { options.UseHttps("devcert.pfx"); });
+ options.ListenAnyIP(7443, options => options.UseHttps("devcert.pfx"));
#endif
- serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(3000);
- });
- webBuilder.UseStartup();
- });
-try
-{
- var app = builder.Build();
+});
- var jobManagerV2 = app.Services.GetRequiredService();
- foreach (var cronJob in CronJobCollector.GetAllCronJobs())
- {
- jobManagerV2.AddOrUpdate(cronJob.Name, cronJob.Job, cronJob.Schedule);
- }
+var config = builder.GetAndRegisterOpenShockConfig();
+builder.Services.AddOpenShockServices(config);
- await app.RunAsync();
+builder.Services.AddHangfire(hangfire =>
+ hangfire.UsePostgreSqlStorage(c =>
+ c.UseNpgsqlConnection(config.Db.Conn)));
+builder.Services.AddHangfireServer();
-}
-catch (Exception e)
+var app = builder.Build();
+
+app.UseCommonOpenShockMiddleware();
+
+app.UseHangfireDashboard(options: new DashboardOptions
{
- Console.WriteLine(e);
-}
\ No newline at end of file
+ AsyncAuthorization = [
+ new DashboardAdminAuth()
+ ]
+});
+
+app.MapControllers();
+
+var jobManager = app.Services.GetRequiredService();
+foreach (var cronJob in CronJobCollector.GetAllCronJobs())
+{
+ jobManager.AddOrUpdate(cronJob.Name, cronJob.Job, cronJob.Schedule);
+}
+
+app.Run();
\ No newline at end of file
diff --git a/Cron/Properties/launchSettings.json b/Cron/Properties/launchSettings.json
index 19454ce9..0121b90c 100644
--- a/Cron/Properties/launchSettings.json
+++ b/Cron/Properties/launchSettings.json
@@ -1,5 +1,4 @@
{
- "$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Cron": {
"commandName": "Project",
diff --git a/Cron/Startup.cs b/Cron/Startup.cs
deleted file mode 100644
index 1a852840..00000000
--- a/Cron/Startup.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Hangfire;
-using Hangfire.PostgreSql;
-using OpenShock.Common;
-using OpenTelemetry.Trace;
-
-namespace OpenShock.Cron;
-
-public sealed class Startup
-{
- private CronConf _config;
-
- public Startup(IConfiguration configuration)
- {
-#if DEBUG
- var root = (IConfigurationRoot)configuration;
- var debugView = root.GetDebugView();
- Console.WriteLine(debugView);
-#endif
- _config = configuration.GetChildren()
- .First(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))
- .Get() ??
- throw new Exception("Couldn't bind config, check config file");
- }
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddHangfire(hangfire =>
- hangfire.UsePostgreSqlStorage(c =>
- c.UseNpgsqlConnection(_config.Db.Conn)));
- services.AddHangfireServer();
-
-
- services.AddOpenShockServices(_config);
- }
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory,
- ILogger logger)
- {
- app.UseCommonOpenShockMiddleware();
-
- app.UseHangfireDashboard(options: new DashboardOptions
- {
- AsyncAuthorization = [
- new DashboardAdminAuth()
- ]
- });
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
- });
- }
-}
\ No newline at end of file
diff --git a/LiveControlGateway/LifetimeManager/HubLifetime.cs b/LiveControlGateway/LifetimeManager/HubLifetime.cs
index bb42077a..1c50ca6c 100644
--- a/LiveControlGateway/LifetimeManager/HubLifetime.cs
+++ b/LiveControlGateway/LifetimeManager/HubLifetime.cs
@@ -28,7 +28,6 @@ public sealed class HubLifetime : IAsyncDisposable
{
private readonly TimeSpan _waitBetweenTicks;
private readonly ushort _commandDuration;
- private static readonly ILogger Logger = ApplicationLogging.CreateLogger();
private Dictionary _shockerStates = new();
private readonly byte _tps;
@@ -39,19 +38,23 @@ public sealed class HubLifetime : IAsyncDisposable
private readonly IRedisConnectionProvider _redisConnectionProvider;
private readonly IRedisPubService _redisPubService;
+ private readonly ILogger _logger;
+
///
/// DI Constructor
///
///
///
///
+ ///
///
+ ///
///
- ///
public HubLifetime([Range(1, 10)] byte tps, IHubController hubController,
IDbContextFactory dbContextFactory,
IRedisConnectionProvider redisConnectionProvider,
IRedisPubService redisPubService,
+ ILogger logger,
CancellationToken cancellationToken = default)
{
_tps = tps;
@@ -60,6 +63,7 @@ public HubLifetime([Range(1, 10)] byte tps, IHubController hubController,
_dbContextFactory = dbContextFactory;
_redisConnectionProvider = redisConnectionProvider;
_redisPubService = redisPubService;
+ _logger = logger;
_waitBetweenTicks = TimeSpan.FromMilliseconds(Math.Floor((float)1000 / tps));
_commandDuration = (ushort)(_waitBetweenTicks.TotalMilliseconds * 2.5);
@@ -90,14 +94,14 @@ private async Task UpdateLoop()
}
catch (Exception e)
{
- Logger.LogError(e, "Error in Update()");
+ _logger.LogError(e, "Error in Update()");
}
var elapsed = stopwatch.Elapsed;
var waitTime = _waitBetweenTicks - elapsed;
if (waitTime.TotalMilliseconds < 1)
{
- Logger.LogWarning("Update loop running behind for device [{DeviceId}]", _hubController.Id);
+ _logger.LogWarning("Update loop running behind for device [{DeviceId}]", _hubController.Id);
continue;
}
@@ -147,7 +151,7 @@ public async Task UpdateDevice()
///
private async Task UpdateShockers(OpenShockContext db)
{
- Logger.LogDebug("Updating shockers for device [{DeviceId}]", _hubController.Id);
+ _logger.LogDebug("Updating shockers for device [{DeviceId}]", _hubController.Id);
var ownShockers = await db.Shockers.Where(x => x.Device == _hubController.Id).Select(x => new ShockerState()
{
Id = x.Id,
@@ -196,7 +200,7 @@ public ValueTask Control(IEnumerable shocks)
{
if (!_shockerStates.TryGetValue(shock.Id, out var state)) continue;
- Logger.LogTrace(
+ _logger.LogTrace(
"Control exclusive: {Exclusive}, type: {Type}, duration: {Duration}, intensity: {Intensity}",
shock.Exclusive, shock.Type, shock.Duration, shock.Intensity);
state.ExclusiveUntil = shock.Exclusive && shock.Type != ControlType.Stop
diff --git a/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs b/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs
index 1a462456..ee54b981 100644
--- a/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs
+++ b/LiveControlGateway/LifetimeManager/HubLifetimeManager.cs
@@ -18,29 +18,33 @@ namespace OpenShock.LiveControlGateway.LifetimeManager;
///
public sealed class HubLifetimeManager
{
- private readonly ILogger _logger;
private readonly IDbContextFactory _dbContextFactory;
private readonly IRedisConnectionProvider _redisConnectionProvider;
private readonly IRedisPubService _redisPubService;
+ private readonly ILoggerFactory _loggerFactory;
+ private readonly ILogger _logger;
private readonly ConcurrentDictionary _managers = new();
///
/// DI constructor
///
- ///
///
///
///
+ ///
public HubLifetimeManager(
- ILogger logger,
IDbContextFactory dbContextFactory,
IRedisConnectionProvider redisConnectionProvider,
- IRedisPubService redisPubService)
+ IRedisPubService redisPubService,
+ ILoggerFactory loggerFactory
+ )
{
- _logger = logger;
_dbContextFactory = dbContextFactory;
_redisConnectionProvider = redisConnectionProvider;
_redisPubService = redisPubService;
+ _loggerFactory = loggerFactory;
+
+ _logger = _loggerFactory.CreateLogger();
}
///
@@ -65,6 +69,7 @@ public async Task AddDeviceConnection(byte tps, IHubController hubC
_dbContextFactory,
_redisConnectionProvider,
_redisPubService,
+ _loggerFactory.CreateLogger(),
cancellationToken);
await using var db = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
diff --git a/LiveControlGateway/Program.cs b/LiveControlGateway/Program.cs
index 1a6fdf73..6ddf3fb2 100644
--- a/LiveControlGateway/Program.cs
+++ b/LiveControlGateway/Program.cs
@@ -1,51 +1,53 @@
+using OpenShock.Common;
+using OpenShock.Common.Extensions;
+using OpenShock.Common.JsonSerialization;
+using OpenShock.Common.Services.Device;
+using OpenShock.Common.Services.Ota;
+using OpenShock.Common.Utils;
using OpenShock.LiveControlGateway;
-using Serilog;
-
-HostBuilder builder = new();
-builder.UseContentRoot(Directory.GetCurrentDirectory())
- .ConfigureHostConfiguration(config =>
- {
- config.AddEnvironmentVariables(prefix: "DOTNET_");
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .ConfigureAppConfiguration((context, config) =>
- {
- config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
- .AddJsonFile("appsettings.Custom.json", optional: true, reloadOnChange: false)
- .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false);
-
- config.AddUserSecrets(typeof(Program).Assembly);
- config.AddEnvironmentVariables();
- if (args is { Length: > 0 }) config.AddCommandLine(args);
- })
- .UseDefaultServiceProvider((context, options) =>
- {
- var isDevelopment = context.HostingEnvironment.IsDevelopment();
- options.ValidateScopes = isDevelopment;
- options.ValidateOnBuild = isDevelopment;
- })
- .UseSerilog((context, _, config) => { config.ReadFrom.Configuration(context.Configuration); })
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseKestrel();
- webBuilder.ConfigureKestrel(serverOptions =>
- {
+using OpenShock.LiveControlGateway.LifetimeManager;
+using OpenShock.LiveControlGateway.PubSub;
+var builder = OpenShockApplication.CreateDefaultBuilder(args, options =>
+{
#if DEBUG
- serverOptions.ListenAnyIP(580);
- serverOptions.ListenAnyIP(5443, options => { options.UseHttps("devcert.pfx"); });
+ options.ListenAnyIP(580);
+ options.ListenAnyIP(5443, options => options.UseHttps("devcert.pfx"));
#else
- serverOptions.ListenAnyIP(80);
+ options.ListenAnyIP(80);
#endif
- serverOptions.Limits.RequestHeadersTimeout = TimeSpan.FromMilliseconds(3000);
- });
- webBuilder.UseStartup();
+});
+
+var config = builder.GetAndRegisterOpenShockConfig();
+var commonService = builder.Services.AddOpenShockServices(config);
+
+builder.Services.AddSignalR()
+ .AddOpenShockStackExchangeRedis(options => { options.Configuration = commonService.RedisConfig; })
+ .AddJsonProtocol(options =>
+ {
+ options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
+ options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter());
});
-try
-{
- await builder.Build().RunAsync();
-}
-catch (Exception e)
-{
- Console.WriteLine(e);
-}
\ No newline at end of file
+
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+builder.Services.AddSwaggerExt("OpenShock.LiveControlGateway");
+
+builder.Services.ConfigureOptions();
+//services.AddHealthChecks().AddCheck("database");
+
+builder.Services.AddHostedService();
+builder.Services.AddHostedService();
+
+builder.Services.AddSingleton();
+
+var app = builder.Build();
+
+app.UseCommonOpenShockMiddleware();
+
+app.UseSwaggerExt();
+
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/LiveControlGateway/Properties/launchSettings.json b/LiveControlGateway/Properties/launchSettings.json
index 2b883619..0d428b5e 100644
--- a/LiveControlGateway/Properties/launchSettings.json
+++ b/LiveControlGateway/Properties/launchSettings.json
@@ -4,13 +4,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "OPENSHOCK__DB": "Host=docker-node;Port=1337;Database=openshock;Username=root;Password=root",
- "OPENSHOCK__FQDN": "luc-lcg.lucheart.ovh",
- "OPENSHOCK__COUNTRYCODE": "DE",
- "OPENSHOCK__REDIS__HOST":"docker-node",
- //"OPENSHOCK__REDIS__HOST":"localhost",
- "OPENSHOCK__REDIS__PASSWORD": ""
+ "ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
diff --git a/LiveControlGateway/Startup.cs b/LiveControlGateway/Startup.cs
deleted file mode 100644
index 80c69aba..00000000
--- a/LiveControlGateway/Startup.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using System.ComponentModel.DataAnnotations;
-using System.Text.Json;
-using Asp.Versioning.ApiExplorer;
-using Microsoft.OpenApi.Models;
-using OpenShock.Common;
-using OpenShock.Common.Constants;
-using OpenShock.Common.JsonSerialization;
-using OpenShock.Common.Services.Device;
-using OpenShock.Common.Services.Ota;
-using OpenShock.Common.Utils;
-using OpenShock.LiveControlGateway.LifetimeManager;
-using OpenShock.LiveControlGateway.PubSub;
-using JsonSerializer = System.Text.Json.JsonSerializer;
-
-namespace OpenShock.LiveControlGateway;
-
-///
-/// Startup class for the LCG
-///
-public sealed class Startup
-{
- private LCGConfig _lcgConfig;
-
- ///
- /// Setup the LCG, configure config and validate
- ///
- ///
- ///
- public Startup(IConfiguration configuration)
- {
-#if DEBUG
- var root = (IConfigurationRoot)configuration;
- var debugView = root.GetDebugView();
- Console.WriteLine(debugView);
-#endif
- _lcgConfig = configuration.GetChildren().First(x => x.Key.Equals("openshock", StringComparison.InvariantCultureIgnoreCase))
- .Get() ??
- throw new Exception("Couldn't bind config, check config file");
-
- var validator = new ValidationContext(_lcgConfig);
- Validator.ValidateObject(_lcgConfig, validator, true);
-
-#if DEBUG
- Console.WriteLine(JsonSerializer.Serialize(_lcgConfig,
- new JsonSerializerOptions { WriteIndented = true }));
-#endif
- }
-
- ///
- /// Configures the services for the LCG
- ///
- ///
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddSingleton(_lcgConfig);
-
- var commonService = services.AddOpenShockServices(_lcgConfig);
-
- services.AddSignalR()
- .AddOpenShockStackExchangeRedis(options => { options.Configuration = commonService.RedisConfig; })
- .AddJsonProtocol(options =>
- {
- options.PayloadSerializerOptions.PropertyNameCaseInsensitive = true;
- options.PayloadSerializerOptions.Converters.Add(new SemVersionJsonConverter());
- });
-
- services.AddScoped();
- services.AddScoped();
-
- services.AddSwaggerGen(options =>
- {
- options.CustomOperationIds(e =>
- $"{e.ActionDescriptor.RouteValues["controller"]}_{e.ActionDescriptor.RouteValues["action"]}_{e.HttpMethod}");
- options.SchemaFilter();
- options.ParameterFilter();
- options.OperationFilter();
- options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "OpenShock.LiveControlGateway.xml"));
- options.AddSecurityDefinition("OpenShockToken", new OpenApiSecurityScheme
- {
- Name = "OpenShockToken",
- Type = SecuritySchemeType.ApiKey,
- Scheme = "ApiKeyAuth",
- In = ParameterLocation.Header,
- Description = "API Token Authorization header."
- });
- options.AddSecurityRequirement(new OpenApiSecurityRequirement
- {
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = AuthConstants.AuthTokenHeaderName
- }
- },
- Array.Empty()
- }
- });
- options.AddServer(new OpenApiServer { Url = "https://api.openshock.app" });
- options.AddServer(new OpenApiServer { Url = "https://staging-api.openshock.app" });
- options.AddServer(new OpenApiServer { Url = "https://localhost" });
- }
- );
-
- services.ConfigureOptions();
- //services.AddHealthChecks().AddCheck("database");
-
- services.AddHostedService();
- services.AddHostedService();
-
- services.AddSingleton();
-
- }
-
- ///
- /// Register middleware and co.
- ///
- ///
- ///
- ///
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
- {
- ApplicationLogging.LoggerFactory = loggerFactory;
- app.UseCommonOpenShockMiddleware();
-
- app.UseSwagger();
- var provider = app.ApplicationServices.GetRequiredService();
- app.UseSwaggerUI(c =>
- {
- foreach (var description in provider.ApiVersionDescriptions)
- c.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json",
- description.GroupName.ToUpperInvariant());
- });
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
- });
- }
-}
\ No newline at end of file