diff --git a/Moonlight.ApiServer/Database/CoreDataContext.cs b/Moonlight.ApiServer/Database/CoreDataContext.cs index 4a7ea146..5c586842 100644 --- a/Moonlight.ApiServer/Database/CoreDataContext.cs +++ b/Moonlight.ApiServer/Database/CoreDataContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Helpers; @@ -7,7 +8,11 @@ namespace Moonlight.ApiServer.Database; public class CoreDataContext : DatabaseContext { public override string Prefix { get; } = "Core"; - + public DbSet Users { get; set; } public DbSet ApiKeys { get; set; } + + public CoreDataContext(AppConfiguration configuration) : base(configuration) + { + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/DevServer.cs b/Moonlight.ApiServer/DevServer.cs deleted file mode 100644 index 40858309..00000000 --- a/Moonlight.ApiServer/DevServer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; - -namespace Moonlight.ApiServer; - -public static class DevServer -{ - public async static Task Run(string[] args, Assembly[] pluginAssemblies) - { - Console.WriteLine("Preparing development server"); - await Startup.Run(args, pluginAssemblies); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Helpers/ApplicationStateHelper.cs b/Moonlight.ApiServer/Helpers/ApplicationStateHelper.cs deleted file mode 100644 index 1bef88ab..00000000 --- a/Moonlight.ApiServer/Helpers/ApplicationStateHelper.cs +++ /dev/null @@ -1,11 +0,0 @@ -using MoonCore.Services; -using Moonlight.ApiServer.Configuration; - -namespace Moonlight.ApiServer.Helpers; - -public class ApplicationStateHelper -{ - public static AppConfiguration Configuration { get; private set; } - - public static void SetConfiguration(AppConfiguration configuration) => Configuration = configuration; -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Helpers/DatabaseContext.cs b/Moonlight.ApiServer/Helpers/DatabaseContext.cs index 98c30d10..5cb85555 100644 --- a/Moonlight.ApiServer/Helpers/DatabaseContext.cs +++ b/Moonlight.ApiServer/Helpers/DatabaseContext.cs @@ -1,6 +1,4 @@ using Microsoft.EntityFrameworkCore; -using MoonCore.Helpers; -using MoonCore.Services; using Moonlight.ApiServer.Configuration; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; @@ -8,12 +6,13 @@ namespace Moonlight.ApiServer.Helpers; public abstract class DatabaseContext : DbContext { - private AppConfiguration? Configuration; public abstract string Prefix { get; } + + private readonly AppConfiguration Configuration; - public DatabaseContext() + public DatabaseContext(AppConfiguration configuration) { - Configuration = ApplicationStateHelper.Configuration; + Configuration = configuration; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -21,15 +20,6 @@ public abstract class DatabaseContext : DbContext if (optionsBuilder.IsConfigured) return; - // If no config service has been configured, we are probably - // in a EF Core migration, so we need to construct the config manually - if (Configuration == null) - { - Configuration = new ConfigService( - PathBuilder.File("storage", "app.json") - ).Get(); - } - var config = Configuration.Database; var connectionString = $"host={config.Host};" + diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index 2b4af249..e0c5d23d 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -24,8 +24,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs new file mode 100644 index 00000000..f872198a --- /dev/null +++ b/Moonlight.ApiServer/Program.cs @@ -0,0 +1,5 @@ +using Moonlight.ApiServer; + +var startup = new Startup(); + +await startup.Run(args); \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index e6245733..ebf4f374 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -1,8 +1,6 @@ using System.Reflection; -using System.Runtime.Loader; using System.Text.Json; -using MoonCore.Authentication; -using MoonCore.Exceptions; +using MoonCore.Configuration; using MoonCore.Extended.Abstractions; using MoonCore.Extended.Extensions; using MoonCore.Extended.Helpers; @@ -15,6 +13,7 @@ using MoonCore.Extensions; using MoonCore.Helpers; using MoonCore.PluginFramework.Extensions; using MoonCore.Plugins; +using MoonCore.Services; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Helpers; @@ -24,20 +23,80 @@ using Moonlight.ApiServer.Interfaces.Auth; using Moonlight.ApiServer.Interfaces.OAuth2; using Moonlight.ApiServer.Interfaces.Startup; using Moonlight.ApiServer.Services; -using Moonlight.Shared.Http.Responses.OAuth2; namespace Moonlight.ApiServer; -public static class Startup -{ - public static async Task Main(string[] args) - => await Run(args, []); - - public static async Task Run(string[] args, Assembly[]? additionalAssemblies = null) - { - // Cry about it +// Cry about it #pragma warning disable ASP0000 +public class Startup +{ + private string[] Args; + private Assembly[] AdditionalAssemblies; + + // Logging + private ILoggerProvider[] LoggerProviders; + private ILoggerFactory LoggerFactory; + private ILogger Logger; + + // Configuration + private AppConfiguration Configuration; + private ConfigurationService ConfigurationService; + private ConfigurationOptions ConfigurationOptions; + + // WebApplication Stuff + private WebApplication WebApplication; + private WebApplicationBuilder WebApplicationBuilder; + + // Plugin Loading + private PluginService PluginService; + private PluginLoaderService PluginLoaderService; + + private IAppStartup[] PluginAppStartups; + private IDatabaseStartup[] PluginDatabaseStartups; + private IEndpointStartup[] PluginEndpointStartups; + + public async Task Run(string[] args, Assembly[]? additionalAssemblies = null) + { + Args = args; + AdditionalAssemblies = additionalAssemblies ?? []; + + await PrintVersion(); + + await CreateStorage(); + await SetupAppConfiguration(); + await SetupLogging(); + await LoadPlugins(); + await InitializePlugins(); + + await CreateWebApplicationBuilder(); + + await RegisterAppConfiguration(); + await RegisterLogging(); + await RegisterBase(); + await RegisterDatabase(); + await RegisterOAuth2(); + await RegisterCaching(); + await HookPluginBuild(); + + await BuildWebApplication(); + + await PrepareDatabase(); + + await UseBase(); + await UseOAuth2(); + await UseBaseMiddleware(); + await HookPluginConfigure(); + + await MapBase(); + await MapOAuth2(); + await HookPluginEndpoints(); + + await WebApplication.RunAsync(); + } + + private Task PrintVersion() + { // Fancy start console output... yes very fancy :> var rainbow = new Crayon.Rainbow(0.5); foreach (var c in "Moonlight") @@ -52,214 +111,313 @@ public static class Startup Console.WriteLine(); - // Storage i guess - Directory.CreateDirectory(PathBuilder.Dir("storage")); + return Task.CompletedTask; + } - // Configure startup logger - var startupLoggerFactory = new LoggerFactory(); + private Task CreateStorage() + { + Directory.CreateDirectory("storage"); + Directory.CreateDirectory(PathBuilder.Dir("storage", "logs")); + Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins")); - // TODO: Add direct extension method - var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => + return Task.CompletedTask; + } + + #region Base + + private Task RegisterBase() + { + WebApplicationBuilder.Services.AutoAddServices(); + WebApplicationBuilder.Services.AddHttpClient(); + WebApplicationBuilder.Services.AddApiExceptionHandler(); + + // Add pre-existing services + WebApplicationBuilder.Services.AddSingleton(Configuration); + WebApplicationBuilder.Services.AddSingleton(PluginService); + + // Configure controllers + var mvcBuilder = WebApplicationBuilder.Services.AddControllers(); + + // Add plugin and additional assemblies as application parts + foreach (var pluginAssembly in PluginLoaderService.PluginAssemblies) + mvcBuilder.AddApplicationPart(pluginAssembly); + + foreach (var additionalAssembly in AdditionalAssemblies) + mvcBuilder.AddApplicationPart(additionalAssembly); + + return Task.CompletedTask; + } + + private Task UseBase() + { + WebApplication.UseRouting(); + WebApplication.UseExceptionHandler("/"); + + if (Configuration.Client.Enable) + { + if (WebApplication.Environment.IsDevelopment()) + WebApplication.UseWebAssemblyDebugging(); + + WebApplication.UseBlazorFrameworkFiles(); + WebApplication.UseStaticFiles(); + } + + return Task.CompletedTask; + } + + private Task UseBaseMiddleware() + { + WebApplication.UseMiddleware(); + WebApplication.UseMiddleware(); + + return Task.CompletedTask; + } + + private Task MapBase() + { + WebApplication.MapControllers(); + + if (Configuration.Client.Enable) + { + WebApplication.MapFallbackToFile("index.html"); + } + + return Task.CompletedTask; + } + + #endregion + + #region Interfaces + + private Task RegisterInterfaces() + { + WebApplicationBuilder.Services.AddInterfaces(configuration => + { + // We use moonlight itself as a plugin assembly + configuration.AddAssembly(typeof(Startup).Assembly); + + configuration.AddAssemblies(AdditionalAssemblies); + configuration.AddAssemblies(PluginLoaderService.PluginAssemblies); + + configuration.AddInterface(); + configuration.AddInterface(); + }); + + return Task.CompletedTask; + } + + #endregion + + #region Plugin Loading + + private async Task LoadPlugins() + { + // Load plugins + PluginService = new PluginService( + LoggerFactory.CreateLogger() + ); + + await PluginService.Load(); + + // Initialize api server plugin loader + PluginLoaderService = new PluginLoaderService( + LoggerFactory.CreateLogger() + ); + + // Search up entrypoints and assemblies for the apiServer + var assemblyFiles = PluginService.GetAssemblies("apiServer") + .Values + .ToArray(); + + var entrypoints = PluginService.GetEntrypoints("apiServer"); + + // Build source from the retrieved data + PluginLoaderService.AddFilesSource(assemblyFiles, entrypoints); + + // Perform assembly loading + await PluginLoaderService.Load(); + } + + private Task InitializePlugins() + { + var initialisationServiceCollection = new ServiceCollection(); + + // Configure base services for initialisation + initialisationServiceCollection.AddSingleton(Configuration); + + initialisationServiceCollection.AddLogging(builder => { builder.AddProviders(LoggerProviders); }); + + // Configure plugin loading by using the interface service + initialisationServiceCollection.AddInterfaces(configuration => + { + // We use moonlight itself as a plugin assembly + configuration.AddAssembly(typeof(Startup).Assembly); + + configuration.AddAssemblies(PluginLoaderService.PluginAssemblies); + configuration.AddAssemblies(AdditionalAssemblies); + + configuration.AddInterface(); + configuration.AddInterface(); + configuration.AddInterface(); + }); + + var initialisationServiceProvider = initialisationServiceCollection.BuildServiceProvider(); + + PluginAppStartups = initialisationServiceProvider.GetRequiredService(); + PluginDatabaseStartups = initialisationServiceProvider.GetRequiredService(); + PluginEndpointStartups = initialisationServiceProvider.GetRequiredService(); + + return Task.CompletedTask; + } + + #region Hooks + + private async Task HookPluginBuild() + { + foreach (var pluginAppStartup in PluginAppStartups) + { + try + { + await pluginAppStartup.BuildApp(WebApplicationBuilder); + } + catch (Exception e) + { + Logger.LogError( + "An error occured while processing 'BuildApp' for '{name}': {e}", + pluginAppStartup.GetType().FullName, + e + ); + } + } + } + + private async Task HookPluginConfigure() + { + foreach (var pluginAppStartup in PluginAppStartups) + { + try + { + await pluginAppStartup.ConfigureApp(WebApplication); + } + catch (Exception e) + { + Logger.LogError( + "An error occured while processing 'ConfigureApp' for '{name}': {e}", + pluginAppStartup.GetType().FullName, + e + ); + } + } + } + + private async Task HookPluginEndpoints() + { + foreach (var pluginEndpointStartup in PluginEndpointStartups) + { + try + { + await pluginEndpointStartup.ConfigureEndpoints(WebApplication); + } + catch (Exception e) + { + Logger.LogError( + "An error occured while processing 'ConfigureEndpoints' for '{name}': {e}", + pluginEndpointStartup.GetType().FullName, + e + ); + } + } + } + + #endregion + + #endregion + + #region Configurations + + private Task SetupAppConfiguration() + { + ConfigurationService = new ConfigurationService(); + + // Setup options + ConfigurationOptions = new ConfigurationOptions(); + + ConfigurationOptions.AddConfiguration("app"); + ConfigurationOptions.Path = PathBuilder.Dir("storage"); + ConfigurationOptions.EnvironmentPrefix = "MOONLIGHT"; + + // Create minimal logger + var loggerFactory = new LoggerFactory(); + + loggerFactory.AddMoonCore(configuration => { configuration.Console.Enable = true; configuration.Console.EnableAnsiMode = true; configuration.FileLogging.Enable = false; }); - startupLoggerFactory.AddProviders(providers); + var logger = loggerFactory.CreateLogger(); - var startupLogger = startupLoggerFactory.CreateLogger("Startup"); - - // Load plugins - var pluginService = new PluginService( - startupLoggerFactory.CreateLogger() + // Retrieve configuration + Configuration = ConfigurationService.GetConfiguration( + ConfigurationOptions, + logger ); - await pluginService.Load(); - - var pluginAssemblies = await LoadPlugins(pluginService, startupLoggerFactory); - - // Configure startup interfaces - var startupServiceCollection = new ServiceCollection(); - - startupServiceCollection.AddConfiguration(options => - { - options.UsePath(PathBuilder.Dir("storage")); - options.UseEnvironmentPrefix("MOONLIGHT"); - - options.AddConfiguration("app"); - }); - - startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); }); - - startupServiceCollection.AddPlugins(configuration => - { - // Configure startup interfaces - configuration.AddInterface(); - configuration.AddInterface(); - configuration.AddInterface(); - - // Configure assemblies to scan - configuration.AddAssembly(typeof(Startup).Assembly); - - if (additionalAssemblies != null) - configuration.AddAssemblies(additionalAssemblies); - - configuration.AddAssemblies(pluginAssemblies); - }); - - - var startupServiceProvider = startupServiceCollection.BuildServiceProvider(); - var appStartupInterfaces = startupServiceProvider.GetRequiredService(); - - var config = startupServiceProvider.GetRequiredService(); - ApplicationStateHelper.SetConfiguration(config); - - // Start the actual app - var builder = WebApplication.CreateBuilder(args); - - await ConfigureLogging(builder); - - await ConfigureDatabase( - builder, - startupLoggerFactory, - startupServiceProvider.GetRequiredService() - ); - - // Call interfaces - foreach (var startupInterface in appStartupInterfaces) - { - try - { - await startupInterface.BuildApp(builder); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing BuildApp call for interface '{interfaceName}': {e}", - startupInterface.GetType().FullName, - e - ); - } - } - - var controllerBuilder = builder.Services.AddControllers(); - - // Add current assemblies to the application part - foreach (var moduleAssembly in pluginAssemblies) - controllerBuilder.AddApplicationPart(moduleAssembly); - - builder.Services.AddSingleton(config); - builder.Services.AddSingleton(pluginService); - builder.Services.AutoAddServices(typeof(Startup).Assembly); - builder.Services.AddHttpClient(); - - await ConfigureCaching(builder, startupLogger, config); - - await ConfigureOAuth2(builder, startupLogger, config); - - // Implementation service - builder.Services.AddPlugins(configuration => - { - configuration.AddInterface(); - configuration.AddInterface(); - - configuration.AddAssembly(typeof(Startup).Assembly); - - if (additionalAssemblies != null) - configuration.AddAssemblies(additionalAssemblies); - - configuration.AddAssemblies(pluginAssemblies); - }); - - var app = builder.Build(); - - await PrepareDatabase(app); - - if (config.Client.Enable) - { - if (app.Environment.IsDevelopment()) - app.UseWebAssemblyDebugging(); - - app.UseBlazorFrameworkFiles(); - app.UseStaticFiles(); - } - - app.UseRouting(); - - app.UseApiErrorHandling(); - - await UseOAuth2(app); - - // Call interfaces - foreach (var startupInterface in appStartupInterfaces) - { - try - { - await startupInterface.ConfigureApp(app); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}", - startupInterface.GetType().FullName, - e - ); - } - } - - app.UseMiddleware(); - - app.UseMiddleware(); - - // Call interfaces - var endpointStartupInterfaces = startupServiceProvider.GetRequiredService(); - - foreach (var endpointStartup in endpointStartupInterfaces) - { - try - { - await endpointStartup.ConfigureEndpoints(app); - } - catch (Exception e) - { - startupLogger.LogCritical( - "An unhandled error occured while processing ConfigureEndpoints call for interface '{interfaceName}': {e}", - endpointStartup.GetType().FullName, - e - ); - } - } - - app.MapControllers(); - - if (config.Client.Enable) - app.MapFallbackToFile("index.html"); - - await app.RunAsync(); + return Task.CompletedTask; } + private Task RegisterAppConfiguration() + { + ConfigurationService.RegisterInDi(ConfigurationOptions, WebApplicationBuilder.Services); + WebApplicationBuilder.Services.AddSingleton(ConfigurationService); + + return Task.CompletedTask; + } + + #endregion + + #region Web Application + + private Task CreateWebApplicationBuilder() + { + WebApplicationBuilder = WebApplication.CreateBuilder(Args); + return Task.CompletedTask; + } + + private Task BuildWebApplication() + { + WebApplication = WebApplicationBuilder.Build(); + return Task.CompletedTask; + } + + #endregion + #region Logging - public static async Task ConfigureLogging(IHostApplicationBuilder builder) + private Task SetupLogging() { - // Create logging path - Directory.CreateDirectory(PathBuilder.Dir("storage", "logs")); - - // Configure application logging - builder.Logging.ClearProviders(); - - builder.Logging.AddMoonCore(configuration => + LoggerProviders = LoggerBuildHelper.BuildFromConfiguration(configuration => { configuration.Console.Enable = true; configuration.Console.EnableAnsiMode = true; - - configuration.FileLogging.Enable = true; - configuration.FileLogging.Path = PathBuilder.File("storage", "logs", "moonlight.log"); - configuration.FileLogging.EnableLogRotation = true; - configuration.FileLogging.RotateLogNameTemplate = PathBuilder.File("storage", "logs", "moonlight.log.{0}"); + configuration.FileLogging.Enable = false; }); + LoggerFactory = new LoggerFactory(); + LoggerFactory.AddProviders(LoggerProviders); + + Logger = LoggerFactory.CreateLogger(); + + return Task.CompletedTask; + } + + private async Task RegisterLogging() + { + // Configure application logging + WebApplicationBuilder.Logging.ClearProviders(); + WebApplicationBuilder.Logging.AddProviders(LoggerProviders); + // Logging levels var logConfigPath = PathBuilder.File("storage", "logConfig.json"); @@ -278,40 +436,49 @@ public static class Startup await File.WriteAllTextAsync(logConfigPath, logConfig); } - builder.Logging.AddConfiguration(await File.ReadAllTextAsync(logConfigPath)); + // Add logging configuration + WebApplicationBuilder.Logging.AddConfiguration( + await File.ReadAllTextAsync(logConfigPath) + ); + + // Mute exception handler middleware + // https://github.com/dotnet/aspnetcore/issues/19740 + WebApplicationBuilder.Logging.AddFilter( + "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", + LogLevel.Critical + ); } #endregion #region Database - public static async Task ConfigureDatabase(IHostApplicationBuilder builder, ILoggerFactory loggerFactory, - IDatabaseStartup[] databaseStartups) + private async Task RegisterDatabase() { - var logger = loggerFactory.CreateLogger(); + var logger = LoggerFactory.CreateLogger(); var databaseHelper = new DatabaseHelper(logger); var databaseCollection = new DatabaseContextCollection(); - foreach (var databaseStartup in databaseStartups) + foreach (var databaseStartup in PluginDatabaseStartups) await databaseStartup.ConfigureDatabase(databaseCollection); foreach (var database in databaseCollection) { databaseHelper.AddDbContext(database); - builder.Services.AddScoped(database); + WebApplicationBuilder.Services.AddScoped(database); } databaseHelper.GenerateMappings(); - builder.Services.AddSingleton(databaseHelper); - builder.Services.AddScoped(typeof(DatabaseRepository<>)); - builder.Services.AddScoped(typeof(CrudHelper<,>)); + WebApplicationBuilder.Services.AddSingleton(databaseHelper); + WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>)); + WebApplicationBuilder.Services.AddScoped(typeof(CrudHelper<,>)); } - public static async Task PrepareDatabase(IApplicationBuilder builder) + private async Task PrepareDatabase() { - using var scope = builder.ApplicationServices.CreateScope(); + using var scope = WebApplication.Services.CreateScope(); var databaseHelper = scope.ServiceProvider.GetRequiredService(); await databaseHelper.EnsureMigrated(scope.ServiceProvider); @@ -321,39 +488,48 @@ public static class Startup #region OAuth2 - public static Task ConfigureOAuth2(WebApplicationBuilder builder, ILogger logger, AppConfiguration config) + private Task RegisterOAuth2() { - builder.AddOAuth2Authentication(configuration => + WebApplicationBuilder.Services.AddOAuth2Authentication(configuration => { - configuration.AccessSecret = config.Authentication.AccessSecret; - configuration.RefreshSecret = config.Authentication.RefreshSecret; - configuration.RefreshDuration = TimeSpan.FromSeconds(config.Authentication.RefreshDuration); - configuration.RefreshInterval = TimeSpan.FromSeconds(config.Authentication.AccessDuration); - configuration.ClientId = config.Authentication.OAuth2.ClientId; - configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret; - configuration.AuthorizeEndpoint = config.PublicUrl + "/api/_auth/oauth2/authorize"; - configuration.RedirectUri = config.PublicUrl; + configuration.AccessSecret = Configuration.Authentication.AccessSecret; + configuration.RefreshSecret = Configuration.Authentication.RefreshSecret; + configuration.RefreshDuration = TimeSpan.FromSeconds(Configuration.Authentication.RefreshDuration); + configuration.RefreshInterval = TimeSpan.FromSeconds(Configuration.Authentication.AccessDuration); + configuration.ClientId = Configuration.Authentication.OAuth2.ClientId; + configuration.ClientSecret = Configuration.Authentication.OAuth2.ClientSecret; + configuration.AuthorizeEndpoint = Configuration.PublicUrl + "/api/_auth/oauth2/authorize"; + configuration.RedirectUri = Configuration.PublicUrl; }); - builder.Services.AddScoped, LocalOAuth2Provider>(); + WebApplicationBuilder.Services.AddScoped, LocalOAuth2Provider>(); - if (config.Authentication.UseLocalOAuth2) - { - builder.AddLocalOAuth2Provider(config.PublicUrl); - builder.Services.AddScoped, LocalOAuth2Provider>(); - builder.Services.AddScoped, LocalOAuth2Provider>(); - } + if (!Configuration.Authentication.UseLocalOAuth2) + return Task.CompletedTask; + + WebApplicationBuilder.Services.AddLocalOAuth2Provider(Configuration.PublicUrl); + WebApplicationBuilder.Services.AddScoped, LocalOAuth2Provider>(); + WebApplicationBuilder.Services.AddScoped, LocalOAuth2Provider>(); return Task.CompletedTask; } - public static Task UseOAuth2(WebApplication application) + private Task UseOAuth2() { - application.UseOAuth2Authentication(); - application.UseLocalOAuth2Provider(); + WebApplication.UseOAuth2Authentication(); + WebApplication.UseMiddleware(); - application.UseMiddleware(); + return Task.CompletedTask; + } + private Task MapOAuth2() + { + WebApplication.MapOAuth2Authentication(); + + if (!Configuration.Authentication.UseLocalOAuth2) + return Task.CompletedTask; + + WebApplication.MapLocalOAuth2Provider(); return Task.CompletedTask; } @@ -361,31 +537,11 @@ public static class Startup #region Caching - public static Task ConfigureCaching(WebApplicationBuilder builder, ILogger logger, AppConfiguration configuration) + private Task RegisterCaching() { - builder.Services.AddMemoryCache(); + WebApplicationBuilder.Services.AddMemoryCache(); return Task.CompletedTask; } #endregion - - #region Plugin loading - - private static async Task LoadPlugins(PluginService pluginService, ILoggerFactory loggerFactory) - { - var pluginLoader = new PluginLoaderService( - loggerFactory.CreateLogger() - ); - - var assemblyFiles = pluginService.GetAssemblies("apiServer").Values.ToArray(); - var entrypoints = pluginService.GetEntrypoints("apiServer"); - - pluginLoader.AddFilesSource(assemblyFiles, entrypoints); - - await pluginLoader.Load(); - - return pluginLoader.PluginAssemblies; - } - - #endregion } \ No newline at end of file diff --git a/Moonlight.Client/DevClient.cs b/Moonlight.Client/DevClient.cs deleted file mode 100644 index 5212ddc7..00000000 --- a/Moonlight.Client/DevClient.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Reflection; - -namespace Moonlight.Client; - -public class DevClient -{ - public async static Task Run(string[] args, Assembly[] assemblies) - { - Console.WriteLine("Preparing development client"); - await Startup.Run(args, assemblies); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index aa913cc9..87ea6e7c 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/Moonlight.Client/Program.cs b/Moonlight.Client/Program.cs new file mode 100644 index 00000000..6e5b4824 --- /dev/null +++ b/Moonlight.Client/Program.cs @@ -0,0 +1,13 @@ +using Moonlight.Client; + +var startup = new Startup(); + +try +{ + await startup.Run(args); +} +catch (Exception e) +{ + Console.WriteLine(e); + throw; +} \ No newline at end of file diff --git a/Moonlight.Client/Startup.cs b/Moonlight.Client/Startup.cs index af7e9e15..0c125698 100644 --- a/Moonlight.Client/Startup.cs +++ b/Moonlight.Client/Startup.cs @@ -19,22 +19,46 @@ namespace Moonlight.Client; public class Startup { - public static async Task Main(string[] args) - => await Run(args, []); - - public static async Task Run(string[] args, Assembly[] assemblies) + private string[] Args; + private Assembly[] AdditionalAssemblies; + + // Logging + private ILoggerProvider[] LoggerProviders; + private ILoggerFactory LoggerFactory; + private ILogger Logger; + + // WebAssemblyHost + private WebAssemblyHostBuilder WebAssemblyHostBuilder; + private WebAssemblyHost WebAssemblyHost; + + // Plugin Loading + private PluginLoaderService PluginLoaderService; + + public async Task Run(string[] args, Assembly[]? assemblies = null) { - // Build pre run logger - var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => - { - configuration.Console.Enable = true; - configuration.Console.EnableAnsiMode = true; - configuration.FileLogging.Enable = false; - }); + Args = args; + AdditionalAssemblies = assemblies ?? []; - using var loggerFactory = new LoggerFactory(providers); - var logger = loggerFactory.CreateLogger("Startup"); + await PrintVersion(); + await SetupLogging(); + await CreateWebAssemblyHostBuilder(); + + await LoadPlugins(); + + await RegisterLogging(); + await RegisterBase(); + await RegisterOAuth2(); + await RegisterFormComponents(); + await RegisterInterfaces(); + + await BuildWebAssemblyHost(); + + await WebAssemblyHost.RunAsync(); + } + + private Task PrintVersion() + { // Fancy start console output... yes very fancy :> Console.Write("Running "); @@ -50,59 +74,133 @@ public class Startup } Console.WriteLine(); - - // Building app - var builder = WebAssemblyHostBuilder.CreateDefault(args); - // Load plugins - var pluginLoader = new PluginLoaderService( - loggerFactory.CreateLogger() + return Task.CompletedTask; + } + + private Task RegisterBase() + { + WebAssemblyHostBuilder.RootComponents.Add("#app"); + WebAssemblyHostBuilder.RootComponents.Add("head::after"); + + WebAssemblyHostBuilder.Services.AddScoped(_ => + new HttpClient + { + BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress) + } ); - pluginLoader.AddHttpHostedSource($"{builder.HostEnvironment.BaseAddress}api/pluginsStream"); - await pluginLoader.Load(); + WebAssemblyHostBuilder.Services.AddScoped(); + WebAssemblyHostBuilder.Services.AddMoonCoreBlazorTailwind(); + WebAssemblyHostBuilder.Services.AddScoped(); - builder.Services.AddSingleton(pluginLoader); + WebAssemblyHostBuilder.Services.AutoAddServices(); - // Configure application logging - builder.Logging.ClearProviders(); - builder.Logging.AddProviders(providers); + return Task.CompletedTask; + } + + private Task RegisterOAuth2() + { + WebAssemblyHostBuilder.AddTokenAuthentication(); + WebAssemblyHostBuilder.AddOAuth2(); - builder.RootComponents.Add("#app"); - builder.RootComponents.Add("head::after"); - - builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - - builder.AddTokenAuthentication(); - builder.AddOAuth2(); - - builder.Services.AddMoonCoreBlazorTailwind(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - builder.Services.AutoAddServices(); + return Task.CompletedTask; + } + private Task RegisterFormComponents() + { FormComponentRepository.Set(); FormComponentRepository.Set(); - FormComponentRepository.Set(); + + return Task.CompletedTask; + } - // Interface service - builder.Services.AddPlugins(configuration => + #region Interfaces + + private Task RegisterInterfaces() + { + WebAssemblyHostBuilder.Services.AddInterfaces(configuration => { + // We use moonlight itself as a plugin assembly configuration.AddAssembly(typeof(Startup).Assembly); - configuration.AddAssemblies(assemblies); - - configuration.AddAssemblies(pluginLoader.PluginAssemblies); + configuration.AddAssemblies(AdditionalAssemblies); + configuration.AddAssemblies(PluginLoaderService.PluginAssemblies); configuration.AddInterface(); configuration.AddInterface(); - configuration.AddInterface(); }); - - var app = builder.Build(); - - await app.RunAsync(); + + return Task.CompletedTask; } + + #endregion + + #region Plugins + + private async Task LoadPlugins() + { + // Initialize api server plugin loader + PluginLoaderService = new PluginLoaderService( + LoggerFactory.CreateLogger() + ); + + // Build source from the retrieved data + var pluginsStreamUrl = $"{WebAssemblyHostBuilder.HostEnvironment.BaseAddress}api/pluginsStream"; + PluginLoaderService.AddHttpHostedSource(pluginsStreamUrl); + + // Perform assembly loading + await PluginLoaderService.Load(); + + // Add plugin loader service to di for the Router/App.razor + WebAssemblyHostBuilder.Services.AddSingleton(PluginLoaderService); + } + + #endregion + + #region Logging + + private Task SetupLogging() + { + LoggerProviders = LoggerBuildHelper.BuildFromConfiguration(configuration => + { + configuration.Console.Enable = true; + configuration.Console.EnableAnsiMode = true; + configuration.FileLogging.Enable = false; + }); + + LoggerFactory = new LoggerFactory(); + LoggerFactory.AddProviders(LoggerProviders); + + Logger = LoggerFactory.CreateLogger(); + + return Task.CompletedTask; + } + + private Task RegisterLogging() + { + WebAssemblyHostBuilder.Logging.ClearProviders(); + WebAssemblyHostBuilder.Logging.AddProviders(LoggerProviders); + + return Task.CompletedTask; + } + + #endregion + + #region Web Application + + private Task CreateWebAssemblyHostBuilder() + { + WebAssemblyHostBuilder = WebAssemblyHostBuilder.CreateDefault(Args); + return Task.CompletedTask; + } + + private Task BuildWebAssemblyHost() + { + WebAssemblyHost = WebAssemblyHostBuilder.Build(); + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/Moonlight.Client/Styles/mappings/mooncore.map b/Moonlight.Client/Styles/mappings/mooncore.map index 4dcb327d..8196a3ba 100755 --- a/Moonlight.Client/Styles/mappings/mooncore.map +++ b/Moonlight.Client/Styles/mappings/mooncore.map @@ -399,5 +399,14 @@ "whitespace-nowrap", "z-10", "z-40", - "z-50" + "z-50", + "sm:max-w-md", + "sm:max-w-lg", + "sm:max-w-xl", + "sm:max-w-2xl", + "sm:max-w-3xl", + "sm:max-w-4xl", + "sm:max-w-5xl", + "sm:max-w-6xl", + "sm:max-w-7xl" ] \ No newline at end of file