diff --git a/LANCommander.Server/Extensions/AspNetExtensions.cs b/LANCommander.Server/Extensions/AspNetExtensions.cs deleted file mode 100644 index dc1038b2..00000000 --- a/LANCommander.Server/Extensions/AspNetExtensions.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Net; -using LANCommander.Server.Services.Models; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.OpenApi.Models; -using Serilog; - -namespace LANCommander.Server; - -public static class AspNetExtensions -{ - public static void AddRazor(this WebApplicationBuilder builder, Settings settings) - { - Log.Debug("Configuring MVC and Blazor"); - builder.Services - .AddMvc(static options => options.EnableEndpointRouting = false) - .AddRazorOptions(static options => - { - options.ViewLocationFormats.Clear(); - options.ViewLocationFormats.Add("/UI/Views/{1}/{0}.cshtml"); - options.ViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); - options.ViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); - - options.AreaViewLocationFormats.Clear(); - options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}.cshtml"); - options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}.cshtml"); - options.AreaViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); - options.AreaViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); - - options.PageViewLocationFormats.Clear(); - options.PageViewLocationFormats.Add("/UI/Pages/{1}/{0}.cshtml"); - options.PageViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); - options.PageViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); - - options.AreaPageViewLocationFormats.Clear(); - options.AreaPageViewLocationFormats.Add("/Areas/{2}/Pages/{1}/{0}.cshtml"); - options.AreaPageViewLocationFormats.Add("/Areas/{2}/Pages/Shared/{0}.cshtml"); - options.AreaPageViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}.cshtml"); - options.AreaPageViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); - options.AreaPageViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); - }); - - builder.Services.AddRazorPages(static options => options.RootDirectory = "/UI/Pages"); - - builder.Services - .AddRazorComponents() - .AddInteractiveServerComponents(); - - builder.Services.AddCascadingAuthenticationState(); - builder.Services.AddAntiforgery(options => - { - options.Cookie.SameSite = settings.Authentication.MinimumSameSitePolicy; - options.Cookie.SecurePolicy = settings.Authentication.CookieSecurePolicy; - }); - } - - public static void AddSignalR(this WebApplicationBuilder builder) - { - builder.Services.AddSignalR().AddJsonProtocol(static options => - { - options.PayloadSerializerOptions.PropertyNamingPolicy = null; - }); - } - - public static void AddCors(this WebApplicationBuilder builder) - { - builder.Services.AddCors(static options => - options.AddPolicy("CorsPolicy", static builder => - { - builder.AllowAnyHeader() - .AllowAnyMethod() - .SetIsOriginAllowed(static (host) => true) - .AllowCredentials(); - }) - ); - } - - public static void AddControllers(this WebApplicationBuilder builder) - { - Log.Debug("Initializing Controllers"); - builder.Services.AddControllers().AddJsonOptions(static x => - { - x.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; - }); - } - - public static void ConfigureKestrel(this WebApplicationBuilder builder) - { - builder.WebHost.ConfigureKestrel(options => - { - var settings = options.ApplicationServices.GetRequiredService(); - - options.Limits.MaxRequestBodySize = long.MaxValue; - options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(5); - - options.Listen(IPAddress.Any, settings.Port); - - if (settings.UseSSL) - { - options.Listen(IPAddress.Any, settings.SSLPort, listenOptions => - { - listenOptions.UseHttps(settings.CertificatePath, settings.CertificatePassword); - }); - } - }); - - builder.Services.Configure(options => - { - options.MultipartBodyLengthLimit = long.MaxValue; - }); - - builder.WebHost.UseStaticWebAssets(); - } -} \ No newline at end of file diff --git a/LANCommander.Server/Program.cs b/LANCommander.Server/Program.cs index e02784f8..94d23b6f 100644 --- a/LANCommander.Server/Program.cs +++ b/LANCommander.Server/Program.cs @@ -1,30 +1,13 @@ -using System.Diagnostics; -using LANCommander.Server.Data; -using LANCommander.Server.Hubs; using LANCommander.Server.Services; -using Microsoft.EntityFrameworkCore; -using Hangfire; -using Microsoft.Data.Sqlite; -using Microsoft.AspNetCore.Http.Features; -using LANCommander.SDK.Enums; using Serilog; -using LANCommander.Server; -using LANCommander.Server.Data.Enums; -using LANCommander.Server.Data.Models; -using LANCommander.Server.Endpoints; -using LANCommander.Server.Jobs.Background; -using LANCommander.Server.Models; using LANCommander.Server.Services.Models; using LANCommander.Server.UI; -using Microsoft.AspNetCore.HttpOverrides; -using Scalar.AspNetCore; -using LANCommander.Server.Services.Importers; using LANCommander.Server.Startup; var builder = WebApplication.CreateBuilder(args); if (args.Contains("--debugger")) - WaitForDebugger(); + builder.WaitForDebugger(); if (args.Contains("--docker")) SettingService.WorkingDirectory = "/app/config"; @@ -32,42 +15,16 @@ if (args.Contains("--docker")) builder.AddAsService(); builder.AddLogger(); -// Add services to the container. -Log.Debug("Loading settings"); - Settings settings; -if (!File.Exists(SettingService.SettingsFile)) -{ - var workingDirectory = Path.GetDirectoryName(SettingService.SettingsFile); - - if (!String.IsNullOrWhiteSpace(workingDirectory)) - Directory.CreateDirectory(workingDirectory); - - settings = new Settings(); - SettingService.SaveSettings(settings); -} -else - settings = SettingService.GetSettings(true); +builder.AddSettings(out settings); -Log.Debug("Validating settings"); -if (settings.Authentication.TokenSecret.Length < 16) -{ - Log.Debug("JWT token secret is too short. Regenerating..."); - settings.Authentication.TokenSecret = Guid.NewGuid().ToString(); - SettingService.SaveSettings(settings); -} -Log.Debug("Done validating settings"); - -builder.Services.AddSingleton(settings); - -ConfigureDatabaseProvider(settings, args); - -builder.AddRazor(settings); +builder.AddRazor(); builder.AddSignalR(); builder.AddCors(); builder.AddControllers(); builder.ConfigureKestrel(); +builder.ConfigureAuthentication(settings); builder.AddIdentity(settings); builder.AddHangfire(); builder.AddOpenApi(); @@ -77,19 +34,12 @@ builder.AddDatabase(); Log.Debug("Building Application"); var app = builder.Build(); +app.UseDatabase(args); + app.UseCors("CorsPolicy"); app.UseHttpsRedirection(); -app.UseMiddleware(); -app.UseMiddleware(); -app.UseMiddleware(); - -app.MapHub("/hubs/gameserver"); - -app.UseForwardedHeaders(new ForwardedHeadersOptions -{ - ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto -}); +app.UseMiddlewares(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) @@ -105,7 +55,7 @@ else app.UseHsts(); } -app.UseHangfireDashboard(); +app.UseHangfire(); // app.UseHttpsRedirection(); app.UseStaticFiles(); @@ -117,185 +67,22 @@ app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); -app.UseMvcWithDefaultRoute(); - -Log.Debug("Registering Endpoints"); - -app.MapHub("/logging"); +app.UseSignalR(); app.UseStaticFiles(); app.MapScalar(); - -app.UseEndpoints(endpoints => -{ - endpoints.MapDownloadEndpoints(); - endpoints.MapSaveEndpoints(); - endpoints.MapControllers(); - endpoints.MapFallbackToPage("/_Host"); -}); +app.MapEndpoints(); app.MapRazorComponents() .AddInteractiveServerRenderMode(); -PrepareDirectories(app); +app.PrepareDirectories(); -await EnsureDatabase(app); +await app.MigrateDatabaseAsync(); +await app.StartServerProcessesAsync(); -await InitializeServerProcesses(app); - -BackgroundJob.Enqueue(x => x.ExecuteAsync()); +app.GenerateThumbnails(); app.Run(); -static void WaitForDebugger() -{ - var currentProcess = Process.GetCurrentProcess(); - - Console.WriteLine($"Waiting for debugger to attach... Process ID: {currentProcess.Id}"); - - while (!Debugger.IsAttached) - { - Thread.Sleep(100); - } - - Console.WriteLine("Debugger attached."); -} - -static void ConfigureDatabaseProvider(Settings settings, string[] args) -{ - var databaseProviderParameter = args.FirstOrDefault(arg => arg.StartsWith("--database-provider="))?.Split('=', 2).Last(); - var connectionStringParameter = args.FirstOrDefault(arg => arg.StartsWith("--connection-string="))?.Split('=', 2).Last(); - - if (!String.IsNullOrWhiteSpace(databaseProviderParameter)) - DatabaseContext.Provider = Enum.Parse(databaseProviderParameter); - else - DatabaseContext.Provider = settings.DatabaseProvider; - - if (!String.IsNullOrWhiteSpace(connectionStringParameter)) - DatabaseContext.ConnectionString = connectionStringParameter; - else - DatabaseContext.ConnectionString = settings.DatabaseConnectionString; -} - -static void PrepareDirectories(WebApplication app) -{ - var settings = app.Services.GetRequiredService(); - var logger = app.Services.GetRequiredService>(); - logger.LogDebug("Ensuring required directories exist"); - - IEnumerable directories = [ - settings.Update.StoragePath, - settings.Launcher.StoragePath, - settings.Backups.StoragePath, - "Snippets", - ]; - - foreach (var directory in directories) - { - logger.LogDebug("Ensuring directory {Directory} exists", directory); - if (!Directory.Exists(directory)) - Directory.CreateDirectory(directory); - } -} - -static async Task EnsureDatabase(WebApplication app) -{ - // Migrate - if (DatabaseContext.Provider != DatabaseProvider.Unknown) - { - using var scope = app.Services.CreateAsyncScope(); - using var db = scope.ServiceProvider.GetRequiredService(); - var logger = scope.ServiceProvider.GetRequiredService>(); - var settings = scope.ServiceProvider.GetRequiredService(); - logger.LogDebug("Migrating database if required"); - - if ((await db.Database.GetPendingMigrationsAsync()).Any()) - { - if (DatabaseContext.Provider == DatabaseProvider.SQLite) - { - var dataSource = new SqliteConnectionStringBuilder(settings.DatabaseConnectionString).DataSource; - - var backupName = Path.Combine("Backups", $"LANCommander.db.{DateTime.Now.ToString("dd-MM-yyyy-HH.mm.ss.bak")}"); - - if (File.Exists(dataSource)) - { - Log.Information("Migrations pending, database will be backed up to {BackupName}", backupName); - File.Copy(dataSource, backupName); - } - } - - await db.Database.MigrateAsync(); - - var storageLocationService = scope.ServiceProvider.GetRequiredService(); - - var archiveLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Archive && l.Default, new StorageLocation - { - Path = "Uploads", - Type = StorageLocationType.Archive, - Default = true, - }); - - if (!Directory.Exists(archiveLocation.Value.Path)) - Directory.CreateDirectory(archiveLocation.Value.Path); - - var mediaLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Media && l.Default, new StorageLocation - { - Path = "Media", - Type = StorageLocationType.Media, - Default = true, - }); - - if (!Directory.Exists(mediaLocation.Value.Path)) - Directory.CreateDirectory(mediaLocation.Value.Path); - - var saveLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Save && l.Default, new StorageLocation - { - Path = "Saves", - Type = StorageLocationType.Save, - Default = true, - }); - - if (!Directory.Exists(saveLocation.Value.Path)) - Directory.CreateDirectory(saveLocation.Value.Path); - } - else - logger.LogDebug("No pending migrations are available. Skipping database migration."); - } -} - -static async Task InitializeServerProcesses(WebApplication app) -{ - if (DatabaseContext.Provider != DatabaseProvider.Unknown) - { - // Autostart any server processes - using var scope = app.Services.CreateScope(); - var serverService = scope.ServiceProvider.GetRequiredService(); - var serverProcessService = scope.ServiceProvider.GetRequiredService(); - var logger = scope.ServiceProvider.GetRequiredService>(); - logger.LogDebug("Autostarting Servers"); - - // Autostart IPX relay - scope.ServiceProvider.GetService(); - - foreach (var server in await serverService.GetAsync(s => s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnApplicationStart)) - { - try - { - logger.LogDebug("Autostarting server {ServerName} with a delay of {AutostartDelay} seconds", server.Name, server.AutostartDelay); - - Task.Run(() => - { - if (server.Autostart && server.AutostartDelay > 0) - Task.Delay(TimeSpan.FromSeconds(server.AutostartDelay)).Wait(); - - return serverProcessService.StartServerAsync(server.Id); - }); - } - catch (Exception ex) - { - logger.LogError(ex, "An unexpected error occurred while trying to autostart the server {ServerName}", server.Name); - } - } - } -} \ No newline at end of file diff --git a/LANCommander.Server/Startup/ApplicationSettings.cs b/LANCommander.Server/Startup/ApplicationSettings.cs new file mode 100644 index 00000000..2c23bb30 --- /dev/null +++ b/LANCommander.Server/Startup/ApplicationSettings.cs @@ -0,0 +1,42 @@ +using LANCommander.Server.Services; +using LANCommander.Server.Services.Models; +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class ApplicationSettings +{ + public static WebApplicationBuilder AddSettings(this WebApplicationBuilder builder, out Settings settings) + { + // Add services to the container. + Log.Debug("Loading settings"); + + if (!File.Exists(SettingService.SettingsFile)) + { + var workingDirectory = Path.GetDirectoryName(SettingService.SettingsFile); + + if (!String.IsNullOrWhiteSpace(workingDirectory)) + Directory.CreateDirectory(workingDirectory); + + settings = new Settings(); + SettingService.SaveSettings(settings); + } + else + settings = SettingService.GetSettings(true); + + Log.Debug("Validating settings"); + + if (settings.Authentication.TokenSecret.Length < 16) + { + Log.Debug("JWT token secret is too short. Regenerating..."); + settings.Authentication.TokenSecret = Guid.NewGuid().ToString(); + SettingService.SaveSettings(settings); + } + + Log.Debug("Done validating settings"); + + builder.Services.AddSingleton(settings); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Extensions/AuthenticationBuilderExtensions.cs b/LANCommander.Server/Startup/Authentication.cs similarity index 84% rename from LANCommander.Server/Extensions/AuthenticationBuilderExtensions.cs rename to LANCommander.Server/Startup/Authentication.cs index 2903e36d..ce937377 100644 --- a/LANCommander.Server/Extensions/AuthenticationBuilderExtensions.cs +++ b/LANCommander.Server/Startup/Authentication.cs @@ -1,24 +1,63 @@ using System.Net.Http.Headers; using System.Security.Claims; using System.Text.Json; -using System.Web; using LANCommander.Server.Data.Models; using LANCommander.Server.Models; using LANCommander.Server.Services; using LANCommander.Server.Services.Models; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Serilog; -using AuthenticationService = LANCommander.Server.Services.AuthenticationService; -namespace LANCommander.Server.Extensions; +namespace LANCommander.Server.Startup; -public static class AuthenticationBuilderExtensions +public static class Authentication { + public static WebApplicationBuilder ConfigureAuthentication(this WebApplicationBuilder builder, Settings settings) + { + builder.Services.AddCascadingAuthenticationState(); + builder.Services.AddAntiforgery(options => + { + options.Cookie.SameSite = settings.Authentication.MinimumSameSitePolicy; + options.Cookie.SecurePolicy = settings.Authentication.CookieSecurePolicy; + }); + + return builder; + } + + public static AuthenticationBuilder AddAuthenticationProviders(this AuthenticationBuilder authBuilder, Settings settings) + { + foreach (var authenticationProvider in settings.Authentication.AuthenticationProviders) + { + try + { + switch (authenticationProvider.Type) + { + case AuthenticationProviderType.OAuth2: + authBuilder.AddOAuth(authenticationProvider); + break; + + case AuthenticationProviderType.OpenIdConnect: + authBuilder.AddOpenIdConnect(authenticationProvider); + break; + + case AuthenticationProviderType.Saml: + throw new NotImplementedException("SAML providers are not supported at this time."); + break; + } + } + catch (Exception ex) + { + Log.Error(ex, "Authentication Provider {Name} could not be registered", + authenticationProvider.Name); + } + } + + return authBuilder; + } + public static AuthenticationBuilder AddOpenIdConnect(this AuthenticationBuilder authBuilder, AuthenticationProvider authenticationProvider) { if (String.IsNullOrWhiteSpace(authenticationProvider.Slug)) diff --git a/LANCommander.Server/Startup/Cors.cs b/LANCommander.Server/Startup/Cors.cs new file mode 100644 index 00000000..a4d1c97a --- /dev/null +++ b/LANCommander.Server/Startup/Cors.cs @@ -0,0 +1,19 @@ +namespace LANCommander.Server.Startup; + +public static class Cors +{ + public static WebApplicationBuilder AddCors(this WebApplicationBuilder builder) + { + builder.Services.AddCors(static options => + options.AddPolicy("CorsPolicy", static builder => + { + builder.AllowAnyHeader() + .AllowAnyMethod() + .SetIsOriginAllowed(static (host) => true) + .AllowCredentials(); + }) + ); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Daemon.cs b/LANCommander.Server/Startup/Daemon.cs new file mode 100644 index 00000000..c326d3b5 --- /dev/null +++ b/LANCommander.Server/Startup/Daemon.cs @@ -0,0 +1,16 @@ +namespace LANCommander.Server.Startup; + +public static class Daemon +{ + public static WebApplicationBuilder AddAsService(this WebApplicationBuilder builder) + { + builder.Services.AddWindowsService(options => + { + options.ServiceName = "LANCommander Server"; + }); + + builder.Services.AddSystemd(); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Database.cs b/LANCommander.Server/Startup/Database.cs new file mode 100644 index 00000000..53960fcc --- /dev/null +++ b/LANCommander.Server/Startup/Database.cs @@ -0,0 +1,107 @@ +using LANCommander.SDK.Enums; +using LANCommander.Server.Data; +using LANCommander.Server.Data.Enums; +using LANCommander.Server.Data.Models; +using LANCommander.Server.Services; +using LANCommander.Server.Services.Models; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class Database +{ + public static WebApplicationBuilder AddDatabase(this WebApplicationBuilder builder) + { + builder.Services.AddDbContextFactory(); + builder.Services.AddDbContext(); + builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + + return builder; + } + + public static WebApplication UseDatabase(this WebApplication app, string[] args) + { + var settings = app.Services.GetService(); + + var databaseProviderParameter = args.FirstOrDefault(arg => arg.StartsWith("--database-provider="))?.Split('=', 2).Last(); + var connectionStringParameter = args.FirstOrDefault(arg => arg.StartsWith("--connection-string="))?.Split('=', 2).Last(); + + if (!String.IsNullOrWhiteSpace(databaseProviderParameter)) + DatabaseContext.Provider = Enum.Parse(databaseProviderParameter); + else + DatabaseContext.Provider = settings.DatabaseProvider; + + if (!String.IsNullOrWhiteSpace(connectionStringParameter)) + DatabaseContext.ConnectionString = connectionStringParameter; + else + DatabaseContext.ConnectionString = settings.DatabaseConnectionString; + + return app; + } + + public static async Task MigrateDatabaseAsync(this WebApplication app) + { + if (DatabaseContext.Provider != DatabaseProvider.Unknown) + { + using var scope = app.Services.CreateAsyncScope(); + using var db = scope.ServiceProvider.GetRequiredService(); + var logger = scope.ServiceProvider.GetRequiredService>(); + var settings = scope.ServiceProvider.GetRequiredService(); + logger.LogDebug("Migrating database if required"); + + if ((await db.Database.GetPendingMigrationsAsync()).Any()) + { + if (DatabaseContext.Provider == DatabaseProvider.SQLite) + { + var dataSource = new SqliteConnectionStringBuilder(settings.DatabaseConnectionString).DataSource; + + var backupName = Path.Combine("Backups", $"LANCommander.db.{DateTime.Now.ToString("dd-MM-yyyy-HH.mm.ss.bak")}"); + + if (File.Exists(dataSource)) + { + Log.Information("Migrations pending, database will be backed up to {BackupName}", backupName); + File.Copy(dataSource, backupName); + } + } + + await db.Database.MigrateAsync(); + + var storageLocationService = scope.ServiceProvider.GetRequiredService(); + + var archiveLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Archive && l.Default, new StorageLocation + { + Path = "Uploads", + Type = StorageLocationType.Archive, + Default = true, + }); + + if (!Directory.Exists(archiveLocation.Value.Path)) + Directory.CreateDirectory(archiveLocation.Value.Path); + + var mediaLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Media && l.Default, new StorageLocation + { + Path = "Media", + Type = StorageLocationType.Media, + Default = true, + }); + + if (!Directory.Exists(mediaLocation.Value.Path)) + Directory.CreateDirectory(mediaLocation.Value.Path); + + var saveLocation = await storageLocationService.AddMissingAsync(l => l.Type == StorageLocationType.Save && l.Default, new StorageLocation + { + Path = "Saves", + Type = StorageLocationType.Save, + Default = true, + }); + + if (!Directory.Exists(saveLocation.Value.Path)) + Directory.CreateDirectory(saveLocation.Value.Path); + } + else + logger.LogDebug("No pending migrations are available. Skipping database migration."); + } + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Debug.cs b/LANCommander.Server/Startup/Debug.cs new file mode 100644 index 00000000..8fec8012 --- /dev/null +++ b/LANCommander.Server/Startup/Debug.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; + +namespace LANCommander.Server.Startup; + +public static class Debug +{ + public static WebApplicationBuilder WaitForDebugger(this WebApplicationBuilder builder) + { + var currentProcess = Process.GetCurrentProcess(); + + Console.WriteLine($"Waiting for debugger to attach... Process ID: {currentProcess.Id}"); + + while (!Debugger.IsAttached) + { + Thread.Sleep(100); + } + + Console.WriteLine("Debugger attached."); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Endpoints.cs b/LANCommander.Server/Startup/Endpoints.cs new file mode 100644 index 00000000..28931b19 --- /dev/null +++ b/LANCommander.Server/Startup/Endpoints.cs @@ -0,0 +1,34 @@ +using LANCommander.Server.Endpoints; +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class Endpoints +{ + public static void AddControllers(this WebApplicationBuilder builder) + { + Log.Debug("Initializing Controllers"); + + builder.Services.AddControllers().AddJsonOptions(static x => + { + x.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; + }); + } + + public static WebApplication MapEndpoints(this WebApplication app) + { + Log.Debug("Registering Endpoints"); + + app.UseMvcWithDefaultRoute(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDownloadEndpoints(); + endpoints.MapSaveEndpoints(); + endpoints.MapControllers(); + endpoints.MapFallbackToPage("/_Host"); + }); + + return app; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Filesystem.cs b/LANCommander.Server/Startup/Filesystem.cs new file mode 100644 index 00000000..9fc1c638 --- /dev/null +++ b/LANCommander.Server/Startup/Filesystem.cs @@ -0,0 +1,30 @@ +using LANCommander.Server.Services.Models; + +namespace LANCommander.Server.Startup; + +public static class Filesystem +{ + public static WebApplication PrepareDirectories(this WebApplication app) + { + var settings = app.Services.GetRequiredService(); + var logger = app.Services.GetRequiredService>(); + + logger.LogDebug("Ensuring required directories exist"); + + IEnumerable directories = [ + settings.Update.StoragePath, + settings.Launcher.StoragePath, + settings.Backups.StoragePath, + "Snippets", + ]; + + foreach (var directory in directories) + { + logger.LogDebug("Ensuring directory {Directory} exists", directory); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + } + + return app; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Hangfire.cs b/LANCommander.Server/Startup/Hangfire.cs new file mode 100644 index 00000000..4fff4661 --- /dev/null +++ b/LANCommander.Server/Startup/Hangfire.cs @@ -0,0 +1,31 @@ +using Hangfire; + +namespace LANCommander.Server.Startup; + +public static class Hangfire +{ + public static WebApplicationBuilder AddHangfire(this WebApplicationBuilder builder) + { + builder.Services.AddHangfire(static (sp, configuration) => + { + var logger = sp.GetRequiredService>(); + logger.LogDebug("Initializing Hangfire"); + configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseInMemoryStorage(); + }); + + builder.Services.AddHangfireServer(); + + return builder; + } + + public static WebApplication UseHangfire(this WebApplication app) + { + app.UseHangfireDashboard(); + + return app; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Extensions/IdentityExtensions.cs b/LANCommander.Server/Startup/Identity.cs similarity index 63% rename from LANCommander.Server/Extensions/IdentityExtensions.cs rename to LANCommander.Server/Startup/Identity.cs index 896d54d4..5c15158d 100644 --- a/LANCommander.Server/Extensions/IdentityExtensions.cs +++ b/LANCommander.Server/Startup/Identity.cs @@ -1,104 +1,72 @@ -using LANCommander.Server.Data.Models; -using LANCommander.Server.Models; -using Microsoft.AspNetCore.Identity; -using Microsoft.IdentityModel.Tokens; -using Serilog; -using System.Text; -using LANCommander.Server.Extensions; -using LANCommander.Server.Services.Models; -using Microsoft.AspNetCore.Authentication; - -namespace LANCommander.Server; - -public static class IdentityExtensions -{ - public static void AddIdentity(this WebApplicationBuilder builder, Settings settings) - { - Log.Debug("Initializing Identity"); - builder.Services.AddIdentityCore((options) => - { - options.SignIn.RequireConfirmedAccount = false; - options.SignIn.RequireConfirmedEmail = false; - - options.Password.RequireNonAlphanumeric = settings.Authentication.PasswordRequireNonAlphanumeric; - options.Password.RequireLowercase = settings.Authentication.PasswordRequireLowercase; - options.Password.RequireUppercase = settings.Authentication.PasswordRequireUppercase; - options.Password.RequireDigit = settings.Authentication.PasswordRequireDigit; - options.Password.RequiredLength = settings.Authentication.PasswordRequiredLength; - }) - .AddRoles() - .AddEntityFrameworkStores() - .AddUserManager>() - .AddSignInManager>() - .AddRoleManager>() - .AddSignInManager() - .AddDefaultTokenProviders(); - - builder.Services - .AddAuthentication(options => - { - options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; - options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; - options.DefaultSignInScheme = IdentityConstants.ExternalScheme; - }) - .AddJwtBearer(options => - { - options.SaveToken = true; - options.RequireHttpsMetadata = false; - options.TokenValidationParameters = new TokenValidationParameters() - { - ValidateIssuer = false, - ValidateAudience = false, - // ValidAudience = configuration["JWT:ValidAudience"], - // ValidIssuer = configuration["JWT:ValidIssuer"], - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.Authentication.TokenSecret)) - }; - }) - .AddAuthenticationProviders(settings) - .AddIdentityCookies(); - - builder.Services.Configure(options => - { - options.Secure = settings.Authentication.CookieSecurePolicy; - options.MinimumSameSitePolicy = settings.Authentication.MinimumSameSitePolicy; - }); - - builder.Services.ConfigureApplicationCookie(options => - { - options.LoginPath = "/Login"; - options.LogoutPath = "/Logout"; - options.AccessDeniedPath = "/AccessDenied"; - }); - } - - public static AuthenticationBuilder AddAuthenticationProviders(this AuthenticationBuilder authBuilder, Settings settings) - { - foreach (var authenticationProvider in settings.Authentication.AuthenticationProviders) - { - try - { - switch (authenticationProvider.Type) - { - case AuthenticationProviderType.OAuth2: - authBuilder.AddOAuth(authenticationProvider); - break; - - case AuthenticationProviderType.OpenIdConnect: - authBuilder.AddOpenIdConnect(authenticationProvider); - break; - - case AuthenticationProviderType.Saml: - throw new NotImplementedException("SAML providers are not supported at this time."); - break; - } - } - catch (Exception ex) - { - Log.Error(ex, "Authentication Provider {Name} could not be registered", - authenticationProvider.Name); - } - } - - return authBuilder; - } -} +using System.Text; +using LANCommander.Server.Data.Models; +using LANCommander.Server.Services.Models; +using Microsoft.AspNetCore.Identity; +using Microsoft.IdentityModel.Tokens; +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class Identity +{ + public static WebApplicationBuilder AddIdentity(this WebApplicationBuilder builder, Settings settings) + { + Log.Debug("Initializing Identity"); + builder.Services.AddIdentityCore((options) => + { + options.SignIn.RequireConfirmedAccount = false; + options.SignIn.RequireConfirmedEmail = false; + + options.Password.RequireNonAlphanumeric = settings.Authentication.PasswordRequireNonAlphanumeric; + options.Password.RequireLowercase = settings.Authentication.PasswordRequireLowercase; + options.Password.RequireUppercase = settings.Authentication.PasswordRequireUppercase; + options.Password.RequireDigit = settings.Authentication.PasswordRequireDigit; + options.Password.RequiredLength = settings.Authentication.PasswordRequiredLength; + }) + .AddRoles() + .AddEntityFrameworkStores() + .AddUserManager>() + .AddSignInManager>() + .AddRoleManager>() + .AddSignInManager() + .AddDefaultTokenProviders(); + + builder.Services + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; + options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; + options.DefaultSignInScheme = IdentityConstants.ExternalScheme; + }) + .AddJwtBearer(options => + { + options.SaveToken = true; + options.RequireHttpsMetadata = false; + options.TokenValidationParameters = new TokenValidationParameters() + { + ValidateIssuer = false, + ValidateAudience = false, + // ValidAudience = configuration["JWT:ValidAudience"], + // ValidIssuer = configuration["JWT:ValidIssuer"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.Authentication.TokenSecret)) + }; + }) + .AddAuthenticationProviders(settings) + .AddIdentityCookies(); + + builder.Services.Configure(options => + { + options.Secure = settings.Authentication.CookieSecurePolicy; + options.MinimumSameSitePolicy = settings.Authentication.MinimumSameSitePolicy; + }); + + builder.Services.ConfigureApplicationCookie(options => + { + options.LoginPath = "/Login"; + options.LogoutPath = "/Logout"; + options.AccessDeniedPath = "/AccessDenied"; + }); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Kestrel.cs b/LANCommander.Server/Startup/Kestrel.cs new file mode 100644 index 00000000..93927f48 --- /dev/null +++ b/LANCommander.Server/Startup/Kestrel.cs @@ -0,0 +1,38 @@ +using System.Net; +using LANCommander.Server.Services.Models; +using Microsoft.AspNetCore.Http.Features; + +namespace LANCommander.Server.Startup; + +public static class Kestrel +{ + public static WebApplicationBuilder ConfigureKestrel(this WebApplicationBuilder builder) + { + builder.WebHost.ConfigureKestrel(options => + { + var settings = options.ApplicationServices.GetRequiredService(); + + options.Limits.MaxRequestBodySize = long.MaxValue; + options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(5); + + options.Listen(IPAddress.Any, settings.Port); + + if (settings.UseSSL) + { + options.Listen(IPAddress.Any, settings.SSLPort, listenOptions => + { + listenOptions.UseHttps(settings.CertificatePath, settings.CertificatePassword); + }); + } + }); + + builder.Services.Configure(options => + { + options.MultipartBodyLengthLimit = long.MaxValue; + }); + + builder.WebHost.UseStaticWebAssets(); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Extensions/LoggerExtensions.cs b/LANCommander.Server/Startup/Logger.cs similarity index 91% rename from LANCommander.Server/Extensions/LoggerExtensions.cs rename to LANCommander.Server/Startup/Logger.cs index 52b22f40..37bc9761 100644 --- a/LANCommander.Server/Extensions/LoggerExtensions.cs +++ b/LANCommander.Server/Startup/Logger.cs @@ -1,72 +1,73 @@ -using Elastic.Serilog.Sinks; -using LANCommander.Server.Hubs; -using Serilog.Sinks.AspNetCore.App.SignalR.Extensions; -using Serilog; -using LANCommander.Server.Models; -using LANCommander.Server.Services.Models; -using Serilog.Events; -using Serilog.Filters; - -namespace LANCommander.Server; - -public static class LoggerExtensions -{ - public static void AddLogger(this WebApplicationBuilder builder) - { - Log.Logger = new LoggerConfiguration() - .WriteTo.Console() - .CreateBootstrapLogger(); - - builder.Services.AddSerilogHub(); - - builder.Services.AddSerilog((serviceProvider, config) => - { - var settings = serviceProvider.GetRequiredService(); - - if (settings.Logs.IgnorePings) - config.Filter.ByExcluding(Matching.WithProperty("RequestPath", v => v.StartsWith("/api/Ping", StringComparison.OrdinalIgnoreCase))); - - foreach (var provider in settings.Logs.Providers) - { - LogEventLevel minimumLevel = provider.MinimumLevel switch - { - LogLevel.Trace => LogEventLevel.Debug, - LogLevel.Debug => LogEventLevel.Debug, - LogLevel.Warning => LogEventLevel.Warning, - LogLevel.Information => LogEventLevel.Information, - LogLevel.Error => LogEventLevel.Error, - LogLevel.Critical => LogEventLevel.Fatal, - _ => LogEventLevel.Information - }; - - switch (provider.Type) - { - case LoggingProviderType.Console: - config.WriteTo.Console(restrictedToMinimumLevel: minimumLevel); - break; - - case LoggingProviderType.SignalR: - config.WriteTo.SignalR( - serviceProvider, - (context, message, logEvent) => LoggingHub.Log(context, message, logEvent)); - break; - - case LoggingProviderType.File: - config.WriteTo.File( - Path.Combine(provider.ConnectionString,"log-.txt"), - rollingInterval: (RollingInterval)(int)(provider.ArchiveEvery ?? LogInterval.Day), - restrictedToMinimumLevel: minimumLevel); - break; - - case LoggingProviderType.Seq: - config.WriteTo.Seq(provider.ConnectionString, restrictedToMinimumLevel: minimumLevel); - break; - - case LoggingProviderType.ElasticSearch: - config.WriteTo.Elasticsearch([new Uri(provider.ConnectionString)], restrictedToMinimumLevel: minimumLevel); - break; - } - } - }); - } -} +using Elastic.Serilog.Sinks; +using LANCommander.Server.Hubs; +using LANCommander.Server.Services.Models; +using Serilog; +using Serilog.Events; +using Serilog.Filters; +using Serilog.Sinks.AspNetCore.App.SignalR.Extensions; + +namespace LANCommander.Server.Startup; + +public static class Logger +{ + public static WebApplicationBuilder AddLogger(this WebApplicationBuilder builder) + { + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .CreateBootstrapLogger(); + + builder.Services.AddSerilogHub(); + + builder.Services.AddSerilog((serviceProvider, config) => + { + var settings = serviceProvider.GetRequiredService(); + + if (settings.Logs.IgnorePings) + config.Filter.ByExcluding(Matching.WithProperty("RequestPath", v => v.StartsWith("/api/Ping", StringComparison.OrdinalIgnoreCase))); + + foreach (var provider in settings.Logs.Providers) + { + LogEventLevel minimumLevel = provider.MinimumLevel switch + { + LogLevel.Trace => LogEventLevel.Debug, + LogLevel.Debug => LogEventLevel.Debug, + LogLevel.Warning => LogEventLevel.Warning, + LogLevel.Information => LogEventLevel.Information, + LogLevel.Error => LogEventLevel.Error, + LogLevel.Critical => LogEventLevel.Fatal, + _ => LogEventLevel.Information + }; + + switch (provider.Type) + { + case LoggingProviderType.Console: + config.WriteTo.Console(restrictedToMinimumLevel: minimumLevel); + break; + + case LoggingProviderType.SignalR: + config.WriteTo.SignalR( + serviceProvider, + (context, message, logEvent) => LoggingHub.Log(context, message, logEvent)); + break; + + case LoggingProviderType.File: + config.WriteTo.File( + Path.Combine(provider.ConnectionString,"log-.txt"), + rollingInterval: (RollingInterval)(int)(provider.ArchiveEvery ?? LogInterval.Day), + restrictedToMinimumLevel: minimumLevel); + break; + + case LoggingProviderType.Seq: + config.WriteTo.Seq(provider.ConnectionString, restrictedToMinimumLevel: minimumLevel); + break; + + case LoggingProviderType.ElasticSearch: + config.WriteTo.Elasticsearch([new Uri(provider.ConnectionString)], restrictedToMinimumLevel: minimumLevel); + break; + } + } + }); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Middleware.cs b/LANCommander.Server/Startup/Middleware.cs new file mode 100644 index 00000000..7ec36638 --- /dev/null +++ b/LANCommander.Server/Startup/Middleware.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.HttpOverrides; + +namespace LANCommander.Server.Startup; + +public static class Middleware +{ + public static WebApplication UseMiddlewares(this WebApplication app) + { + app.UseMiddleware(); + app.UseMiddleware(); + app.UseMiddleware(); + + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + + return app; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Razor.cs b/LANCommander.Server/Startup/Razor.cs new file mode 100644 index 00000000..8496cd5d --- /dev/null +++ b/LANCommander.Server/Startup/Razor.cs @@ -0,0 +1,47 @@ +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class Razor +{ + public static WebApplicationBuilder AddRazor(this WebApplicationBuilder builder) + { + Log.Debug("Configuring MVC and Blazor"); + + builder.Services + .AddMvc(static options => options.EnableEndpointRouting = false) + .AddRazorOptions(static options => + { + options.ViewLocationFormats.Clear(); + options.ViewLocationFormats.Add("/UI/Views/{1}/{0}.cshtml"); + options.ViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); + options.ViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); + + options.AreaViewLocationFormats.Clear(); + options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}.cshtml"); + options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}.cshtml"); + options.AreaViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); + options.AreaViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); + + options.PageViewLocationFormats.Clear(); + options.PageViewLocationFormats.Add("/UI/Pages/{1}/{0}.cshtml"); + options.PageViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); + options.PageViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); + + options.AreaPageViewLocationFormats.Clear(); + options.AreaPageViewLocationFormats.Add("/Areas/{2}/Pages/{1}/{0}.cshtml"); + options.AreaPageViewLocationFormats.Add("/Areas/{2}/Pages/Shared/{0}.cshtml"); + options.AreaPageViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}.cshtml"); + options.AreaPageViewLocationFormats.Add("/UI/Pages/Shared/{0}.cshtml"); + options.AreaPageViewLocationFormats.Add("/UI/Views/Shared/{0}.cshtml"); + }); + + builder.Services.AddRazorPages(static options => options.RootDirectory = "/UI/Pages"); + + builder.Services + .AddRazorComponents() + .AddInteractiveServerComponents(); + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Servers.cs b/LANCommander.Server/Startup/Servers.cs new file mode 100644 index 00000000..c42116cf --- /dev/null +++ b/LANCommander.Server/Startup/Servers.cs @@ -0,0 +1,46 @@ +using LANCommander.SDK.Enums; +using LANCommander.Server.Data; +using LANCommander.Server.Data.Enums; +using LANCommander.Server.Services; + +namespace LANCommander.Server.Startup; + +public static class Servers +{ + public static async Task StartServerProcessesAsync(this WebApplication app) + { + if (DatabaseContext.Provider != DatabaseProvider.Unknown) + { + // Autostart any server processes + using var scope = app.Services.CreateScope(); + var serverService = scope.ServiceProvider.GetRequiredService(); + var serverProcessService = scope.ServiceProvider.GetRequiredService(); + var logger = scope.ServiceProvider.GetRequiredService>(); + + logger.LogDebug("Autostarting Servers"); + + // Autostart IPX relay + scope.ServiceProvider.GetService(); + + foreach (var server in await serverService.GetAsync(s => s.Autostart && s.AutostartMethod == ServerAutostartMethod.OnApplicationStart)) + { + try + { + logger.LogDebug("Autostarting server {ServerName} with a delay of {AutostartDelay} seconds", server.Name, server.AutostartDelay); + + Task.Run(() => + { + if (server.Autostart && server.AutostartDelay > 0) + Task.Delay(TimeSpan.FromSeconds(server.AutostartDelay)).Wait(); + + return serverProcessService.StartServerAsync(server.Id); + }); + } + catch (Exception ex) + { + logger.LogError(ex, "An unexpected error occurred while trying to autostart the server {ServerName}", server.Name); + } + } + } + } +} \ No newline at end of file diff --git a/LANCommander.Server/Extensions/ServiceExtensions.cs b/LANCommander.Server/Startup/Services.cs similarity index 67% rename from LANCommander.Server/Extensions/ServiceExtensions.cs rename to LANCommander.Server/Startup/Services.cs index a88a2b67..5a222960 100644 --- a/LANCommander.Server/Extensions/ServiceExtensions.cs +++ b/LANCommander.Server/Startup/Services.cs @@ -1,104 +1,72 @@ -using LANCommander.SDK; -using LANCommander.Server.Services.MediaGrabbers; -using LANCommander.Server.Services; -using Serilog; -using Hangfire; -using LANCommander.Server.Data; -using LANCommander.Server.Services.Factories; -using LANCommander.Server.Services.Importers; -using LANCommander.Server.Services.Models; - -namespace LANCommander.Server; - -public static class ServiceExtensions -{ - public static void AddLANCommanderServices(this WebApplicationBuilder builder, Settings settings) - { - Log.Debug("Registering services"); - builder.Services.AddSingleton(new Client("", "")); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddTransient(); - builder.Services.AddScoped(typeof(ImportService<>)); - - // Register importers - builder.Services.AddScoped, GameImporter>(); - builder.Services.AddScoped, ServerImporter>(); - builder.Services.AddScoped, RedistributableImporter>(); - - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - builder.Services.AddAutoMapper(typeof(LANCommanderMappingProfile)); - builder.Services.AddFusionCache(); - builder.Services.AddAntDesign(); - builder.Services.AddHttpClient(); - builder.Services.AddHttpContextAccessor(); - - if (settings.Beacon.Enabled) - { - Log.Debug("The beacons have been lit! LANCommander calls for players!"); - builder.Services.AddHostedService(); - } - } - - public static void AddAsService(this WebApplicationBuilder builder) - { - builder.Services.AddWindowsService(options => - { - options.ServiceName = "LANCommander Server"; - }); - - builder.Services.AddSystemd(); - } - - public static void AddHangfire(this WebApplicationBuilder builder) - { - builder.Services.AddHangfire(static (sp, configuration) => - { - var logger = sp.GetRequiredService>(); - logger.LogDebug("Initializing Hangfire"); - configuration - .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) - .UseSimpleAssemblyNameTypeSerializer() - .UseRecommendedSerializerSettings() - .UseInMemoryStorage(); - }); - builder.Services.AddHangfireServer(); - } - - public static void AddDatabase(this WebApplicationBuilder builder) - { - builder.Services.AddDbContextFactory(); - builder.Services.AddDbContext(); - builder.Services.AddDatabaseDeveloperPageExceptionFilter(); - } -} +using LANCommander.SDK; +using LANCommander.Server.Services; +using LANCommander.Server.Services.Factories; +using LANCommander.Server.Services.Importers; +using LANCommander.Server.Services.MediaGrabbers; +using LANCommander.Server.Services.Models; +using Serilog; + +namespace LANCommander.Server.Startup; + +public static class Services +{ + public static WebApplicationBuilder AddLANCommanderServices(this WebApplicationBuilder builder, Settings settings) + { + Log.Debug("Registering services"); + builder.Services.AddSingleton(new Client("", "")); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddTransient(); + builder.Services.AddScoped(typeof(ImportService<>)); + + // Register importers + builder.Services.AddScoped, GameImporter>(); + builder.Services.AddScoped, ServerImporter>(); + builder.Services.AddScoped, RedistributableImporter>(); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + builder.Services.AddAutoMapper(typeof(LANCommanderMappingProfile)); + builder.Services.AddFusionCache(); + builder.Services.AddAntDesign(); + builder.Services.AddHttpClient(); + builder.Services.AddHttpContextAccessor(); + + if (settings.Beacon.Enabled) + { + Log.Debug("The beacons have been lit! LANCommander calls for players!"); + builder.Services.AddHostedService(); + } + + return builder; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/SignalR.cs b/LANCommander.Server/Startup/SignalR.cs new file mode 100644 index 00000000..39980427 --- /dev/null +++ b/LANCommander.Server/Startup/SignalR.cs @@ -0,0 +1,22 @@ +using LANCommander.Server.Hubs; + +namespace LANCommander.Server.Startup; + +public static class SignalR +{ + public static void AddSignalR(this WebApplicationBuilder builder) + { + builder.Services.AddSignalR().AddJsonProtocol(static options => + { + options.PayloadSerializerOptions.PropertyNamingPolicy = null; + }); + } + + public static WebApplication UseSignalR(this WebApplication app) + { + app.MapHub("/hubs/gameserver"); + app.MapHub("/logging"); + + return app; + } +} \ No newline at end of file diff --git a/LANCommander.Server/Startup/Thumbnails.cs b/LANCommander.Server/Startup/Thumbnails.cs new file mode 100644 index 00000000..ec1c4ee9 --- /dev/null +++ b/LANCommander.Server/Startup/Thumbnails.cs @@ -0,0 +1,14 @@ +using Hangfire; +using LANCommander.Server.Jobs.Background; + +namespace LANCommander.Server.Startup; + +public static class Thumbnails +{ + public static WebApplication GenerateThumbnails(this WebApplication app) + { + BackgroundJob.Enqueue(x => x.ExecuteAsync()); + + return app; + } +} \ No newline at end of file