Upgraded packages. Improved startup. Removed unused components

This commit is contained in:
Masu-Baumgartner
2024-11-20 17:21:17 +01:00
parent 2d0a0e53c0
commit fe31c01a06
12 changed files with 598 additions and 357 deletions

View File

@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Helpers; using Moonlight.ApiServer.Helpers;
@@ -10,4 +11,8 @@ public class CoreDataContext : DatabaseContext
public DbSet<User> Users { get; set; } public DbSet<User> Users { get; set; }
public DbSet<ApiKey> ApiKeys { get; set; } public DbSet<ApiKey> ApiKeys { get; set; }
public CoreDataContext(AppConfiguration configuration) : base(configuration)
{
}
} }

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -1,6 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MoonCore.Helpers;
using MoonCore.Services;
using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Configuration;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
@@ -8,12 +6,13 @@ namespace Moonlight.ApiServer.Helpers;
public abstract class DatabaseContext : DbContext public abstract class DatabaseContext : DbContext
{ {
private AppConfiguration? Configuration;
public abstract string Prefix { get; } public abstract string Prefix { get; }
public DatabaseContext() private readonly AppConfiguration Configuration;
public DatabaseContext(AppConfiguration configuration)
{ {
Configuration = ApplicationStateHelper.Configuration; Configuration = configuration;
} }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -21,15 +20,6 @@ public abstract class DatabaseContext : DbContext
if (optionsBuilder.IsConfigured) if (optionsBuilder.IsConfigured)
return; 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<AppConfiguration>(
PathBuilder.File("storage", "app.json")
).Get();
}
var config = Configuration.Database; var config = Configuration.Database;
var connectionString = $"host={config.Host};" + var connectionString = $"host={config.Host};" +

View File

@@ -24,8 +24,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MoonCore" Version="1.7.8" /> <PackageReference Include="MoonCore" Version="1.7.8" />
<PackageReference Include="MoonCore.Extended" Version="1.1.7" /> <PackageReference Include="MoonCore.Extended" Version="1.2.1" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.4" /> <PackageReference Include="MoonCore.PluginFramework" Version="1.0.5" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />

View File

@@ -0,0 +1,5 @@
using Moonlight.ApiServer;
var startup = new Startup();
await startup.Run(args);

View File

@@ -1,8 +1,6 @@
using System.Reflection; using System.Reflection;
using System.Runtime.Loader;
using System.Text.Json; using System.Text.Json;
using MoonCore.Authentication; using MoonCore.Configuration;
using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions; using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Extensions; using MoonCore.Extended.Extensions;
using MoonCore.Extended.Helpers; using MoonCore.Extended.Helpers;
@@ -15,6 +13,7 @@ using MoonCore.Extensions;
using MoonCore.Helpers; using MoonCore.Helpers;
using MoonCore.PluginFramework.Extensions; using MoonCore.PluginFramework.Extensions;
using MoonCore.Plugins; using MoonCore.Plugins;
using MoonCore.Services;
using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Configuration;
using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Database.Entities;
using Moonlight.ApiServer.Helpers; using Moonlight.ApiServer.Helpers;
@@ -24,20 +23,80 @@ using Moonlight.ApiServer.Interfaces.Auth;
using Moonlight.ApiServer.Interfaces.OAuth2; using Moonlight.ApiServer.Interfaces.OAuth2;
using Moonlight.ApiServer.Interfaces.Startup; using Moonlight.ApiServer.Interfaces.Startup;
using Moonlight.ApiServer.Services; using Moonlight.ApiServer.Services;
using Moonlight.Shared.Http.Responses.OAuth2;
namespace Moonlight.ApiServer; namespace Moonlight.ApiServer;
public static class Startup // Cry about it
{
public static async Task Main(string[] args)
=> await Run(args, []);
public static async Task Run(string[] args, Assembly[]? additionalAssemblies = null)
{
// Cry about it
#pragma warning disable ASP0000 #pragma warning disable ASP0000
public class Startup
{
private string[] Args;
private Assembly[] AdditionalAssemblies;
// Logging
private ILoggerProvider[] LoggerProviders;
private ILoggerFactory LoggerFactory;
private ILogger<Startup> 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 :> // Fancy start console output... yes very fancy :>
var rainbow = new Crayon.Rainbow(0.5); var rainbow = new Crayon.Rainbow(0.5);
foreach (var c in "Moonlight") foreach (var c in "Moonlight")
@@ -52,214 +111,313 @@ public static class Startup
Console.WriteLine(); Console.WriteLine();
// Storage i guess return Task.CompletedTask;
Directory.CreateDirectory(PathBuilder.Dir("storage")); }
// Configure startup logger private Task CreateStorage()
var startupLoggerFactory = new LoggerFactory(); {
Directory.CreateDirectory("storage");
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins"));
// TODO: Add direct extension method return Task.CompletedTask;
var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => }
#region Base
private Task RegisterBase()
{
WebApplicationBuilder.Services.AutoAddServices<Startup>();
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<AuthorizationMiddleware>();
WebApplication.UseMiddleware<ApiAuthenticationMiddleware>();
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<IOAuth2Provider>();
configuration.AddInterface<IAuthInterceptor>();
});
return Task.CompletedTask;
}
#endregion
#region Plugin Loading
private async Task LoadPlugins()
{
// Load plugins
PluginService = new PluginService(
LoggerFactory.CreateLogger<PluginService>()
);
await PluginService.Load();
// Initialize api server plugin loader
PluginLoaderService = new PluginLoaderService(
LoggerFactory.CreateLogger<PluginLoaderService>()
);
// 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<IAppStartup>();
configuration.AddInterface<IDatabaseStartup>();
configuration.AddInterface<IEndpointStartup>();
});
var initialisationServiceProvider = initialisationServiceCollection.BuildServiceProvider();
PluginAppStartups = initialisationServiceProvider.GetRequiredService<IAppStartup[]>();
PluginDatabaseStartups = initialisationServiceProvider.GetRequiredService<IDatabaseStartup[]>();
PluginEndpointStartups = initialisationServiceProvider.GetRequiredService<IEndpointStartup[]>();
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<AppConfiguration>("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.Enable = true;
configuration.Console.EnableAnsiMode = true; configuration.Console.EnableAnsiMode = true;
configuration.FileLogging.Enable = false; configuration.FileLogging.Enable = false;
}); });
startupLoggerFactory.AddProviders(providers); var logger = loggerFactory.CreateLogger<ConfigurationService>();
var startupLogger = startupLoggerFactory.CreateLogger("Startup"); // Retrieve configuration
Configuration = ConfigurationService.GetConfiguration<AppConfiguration>(
// Load plugins ConfigurationOptions,
var pluginService = new PluginService( logger
startupLoggerFactory.CreateLogger<PluginService>()
); );
await pluginService.Load(); return Task.CompletedTask;
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<AppConfiguration>("app");
});
startupServiceCollection.AddLogging(loggingBuilder => { loggingBuilder.AddProviders(providers); });
startupServiceCollection.AddPlugins(configuration =>
{
// Configure startup interfaces
configuration.AddInterface<IAppStartup>();
configuration.AddInterface<IDatabaseStartup>();
configuration.AddInterface<IEndpointStartup>();
// 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<IAppStartup[]>();
var config = startupServiceProvider.GetRequiredService<AppConfiguration>();
ApplicationStateHelper.SetConfiguration(config);
// Start the actual app
var builder = WebApplication.CreateBuilder(args);
await ConfigureLogging(builder);
await ConfigureDatabase(
builder,
startupLoggerFactory,
startupServiceProvider.GetRequiredService<IDatabaseStartup[]>()
);
// 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(); private Task RegisterAppConfiguration()
// 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<IOAuth2Provider>(); ConfigurationService.RegisterInDi(ConfigurationOptions, WebApplicationBuilder.Services);
configuration.AddInterface<IAuthInterceptor>(); WebApplicationBuilder.Services.AddSingleton(ConfigurationService);
configuration.AddAssembly(typeof(Startup).Assembly); return Task.CompletedTask;
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(); #endregion
app.UseApiErrorHandling(); #region Web Application
await UseOAuth2(app); private Task CreateWebApplicationBuilder()
// Call interfaces
foreach (var startupInterface in appStartupInterfaces)
{ {
try WebApplicationBuilder = WebApplication.CreateBuilder(Args);
return Task.CompletedTask;
}
private Task BuildWebApplication()
{ {
await startupInterface.ConfigureApp(app); WebApplication = WebApplicationBuilder.Build();
} return Task.CompletedTask;
catch (Exception e)
{
startupLogger.LogCritical(
"An unhandled error occured while processing ConfigureApp call for interface '{interfaceName}': {e}",
startupInterface.GetType().FullName,
e
);
}
} }
app.UseMiddleware<ApiAuthenticationMiddleware>(); #endregion
app.UseMiddleware<AuthorizationMiddleware>();
// Call interfaces
var endpointStartupInterfaces = startupServiceProvider.GetRequiredService<IEndpointStartup[]>();
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();
}
#region Logging #region Logging
public static async Task ConfigureLogging(IHostApplicationBuilder builder) private Task SetupLogging()
{ {
// Create logging path LoggerProviders = LoggerBuildHelper.BuildFromConfiguration(configuration =>
Directory.CreateDirectory(PathBuilder.Dir("storage", "logs"));
// Configure application logging
builder.Logging.ClearProviders();
builder.Logging.AddMoonCore(configuration =>
{ {
configuration.Console.Enable = true; configuration.Console.Enable = true;
configuration.Console.EnableAnsiMode = true; configuration.Console.EnableAnsiMode = true;
configuration.FileLogging.Enable = false;
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}");
}); });
LoggerFactory = new LoggerFactory();
LoggerFactory.AddProviders(LoggerProviders);
Logger = LoggerFactory.CreateLogger<Startup>();
return Task.CompletedTask;
}
private async Task RegisterLogging()
{
// Configure application logging
WebApplicationBuilder.Logging.ClearProviders();
WebApplicationBuilder.Logging.AddProviders(LoggerProviders);
// Logging levels // Logging levels
var logConfigPath = PathBuilder.File("storage", "logConfig.json"); var logConfigPath = PathBuilder.File("storage", "logConfig.json");
@@ -278,40 +436,49 @@ public static class Startup
await File.WriteAllTextAsync(logConfigPath, logConfig); 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 #endregion
#region Database #region Database
public static async Task ConfigureDatabase(IHostApplicationBuilder builder, ILoggerFactory loggerFactory, private async Task RegisterDatabase()
IDatabaseStartup[] databaseStartups)
{ {
var logger = loggerFactory.CreateLogger<DatabaseHelper>(); var logger = LoggerFactory.CreateLogger<DatabaseHelper>();
var databaseHelper = new DatabaseHelper(logger); var databaseHelper = new DatabaseHelper(logger);
var databaseCollection = new DatabaseContextCollection(); var databaseCollection = new DatabaseContextCollection();
foreach (var databaseStartup in databaseStartups) foreach (var databaseStartup in PluginDatabaseStartups)
await databaseStartup.ConfigureDatabase(databaseCollection); await databaseStartup.ConfigureDatabase(databaseCollection);
foreach (var database in databaseCollection) foreach (var database in databaseCollection)
{ {
databaseHelper.AddDbContext(database); databaseHelper.AddDbContext(database);
builder.Services.AddScoped(database); WebApplicationBuilder.Services.AddScoped(database);
} }
databaseHelper.GenerateMappings(); databaseHelper.GenerateMappings();
builder.Services.AddSingleton(databaseHelper); WebApplicationBuilder.Services.AddSingleton(databaseHelper);
builder.Services.AddScoped(typeof(DatabaseRepository<>)); WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>));
builder.Services.AddScoped(typeof(CrudHelper<,>)); 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<DatabaseHelper>(); var databaseHelper = scope.ServiceProvider.GetRequiredService<DatabaseHelper>();
await databaseHelper.EnsureMigrated(scope.ServiceProvider); await databaseHelper.EnsureMigrated(scope.ServiceProvider);
@@ -321,39 +488,48 @@ public static class Startup
#region OAuth2 #region OAuth2
public static Task ConfigureOAuth2(WebApplicationBuilder builder, ILogger logger, AppConfiguration config) private Task RegisterOAuth2()
{ {
builder.AddOAuth2Authentication<User>(configuration => WebApplicationBuilder.Services.AddOAuth2Authentication<User>(configuration =>
{ {
configuration.AccessSecret = config.Authentication.AccessSecret; configuration.AccessSecret = Configuration.Authentication.AccessSecret;
configuration.RefreshSecret = config.Authentication.RefreshSecret; configuration.RefreshSecret = Configuration.Authentication.RefreshSecret;
configuration.RefreshDuration = TimeSpan.FromSeconds(config.Authentication.RefreshDuration); configuration.RefreshDuration = TimeSpan.FromSeconds(Configuration.Authentication.RefreshDuration);
configuration.RefreshInterval = TimeSpan.FromSeconds(config.Authentication.AccessDuration); configuration.RefreshInterval = TimeSpan.FromSeconds(Configuration.Authentication.AccessDuration);
configuration.ClientId = config.Authentication.OAuth2.ClientId; configuration.ClientId = Configuration.Authentication.OAuth2.ClientId;
configuration.ClientSecret = config.Authentication.OAuth2.ClientSecret; configuration.ClientSecret = Configuration.Authentication.OAuth2.ClientSecret;
configuration.AuthorizeEndpoint = config.PublicUrl + "/api/_auth/oauth2/authorize"; configuration.AuthorizeEndpoint = Configuration.PublicUrl + "/api/_auth/oauth2/authorize";
configuration.RedirectUri = config.PublicUrl; configuration.RedirectUri = Configuration.PublicUrl;
}); });
builder.Services.AddScoped<IDataProvider<User>, LocalOAuth2Provider>(); WebApplicationBuilder.Services.AddScoped<IDataProvider<User>, LocalOAuth2Provider>();
if (config.Authentication.UseLocalOAuth2) if (!Configuration.Authentication.UseLocalOAuth2)
{ return Task.CompletedTask;
builder.AddLocalOAuth2Provider<User>(config.PublicUrl);
builder.Services.AddScoped<ILocalProviderImplementation<User>, LocalOAuth2Provider>(); WebApplicationBuilder.Services.AddLocalOAuth2Provider<User>(Configuration.PublicUrl);
builder.Services.AddScoped<IOAuth2Provider<User>, LocalOAuth2Provider<User>>(); WebApplicationBuilder.Services.AddScoped<ILocalProviderImplementation<User>, LocalOAuth2Provider>();
} WebApplicationBuilder.Services.AddScoped<IOAuth2Provider<User>, LocalOAuth2Provider<User>>();
return Task.CompletedTask; return Task.CompletedTask;
} }
public static Task UseOAuth2(WebApplication application) private Task UseOAuth2()
{ {
application.UseOAuth2Authentication<User>(); WebApplication.UseOAuth2Authentication<User>();
application.UseLocalOAuth2Provider<User>(); WebApplication.UseMiddleware<PermissionLoaderMiddleware>();
application.UseMiddleware<PermissionLoaderMiddleware>(); return Task.CompletedTask;
}
private Task MapOAuth2()
{
WebApplication.MapOAuth2Authentication<User>();
if (!Configuration.Authentication.UseLocalOAuth2)
return Task.CompletedTask;
WebApplication.MapLocalOAuth2Provider<User>();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -361,31 +537,11 @@ public static class Startup
#region Caching #region Caching
public static Task ConfigureCaching(WebApplicationBuilder builder, ILogger logger, AppConfiguration configuration) private Task RegisterCaching()
{ {
builder.Services.AddMemoryCache(); WebApplicationBuilder.Services.AddMemoryCache();
return Task.CompletedTask; return Task.CompletedTask;
} }
#endregion #endregion
#region Plugin loading
private static async Task<Assembly[]> LoadPlugins(PluginService pluginService, ILoggerFactory loggerFactory)
{
var pluginLoader = new PluginLoaderService(
loggerFactory.CreateLogger<PluginLoaderService>()
);
var assemblyFiles = pluginService.GetAssemblies("apiServer").Values.ToArray();
var entrypoints = pluginService.GetEntrypoints("apiServer");
pluginLoader.AddFilesSource(assemblyFiles, entrypoints);
await pluginLoader.Load();
return pluginLoader.PluginAssemblies;
}
#endregion
} }

View File

@@ -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);
}
}

View File

@@ -24,8 +24,8 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all"/> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.10" PrivateAssets="all"/>
<PackageReference Include="MoonCore" Version="1.7.8" /> <PackageReference Include="MoonCore" Version="1.7.8" />
<PackageReference Include="MoonCore.Blazor" Version="1.2.7" /> <PackageReference Include="MoonCore.Blazor" Version="1.2.7" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.4" /> <PackageReference Include="MoonCore.PluginFramework" Version="1.0.5" />
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.1.2" /> <PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.1.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -0,0 +1,13 @@
using Moonlight.Client;
var startup = new Startup();
try
{
await startup.Run(args);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}

View File

@@ -19,22 +19,46 @@ namespace Moonlight.Client;
public class Startup public class Startup
{ {
public static async Task Main(string[] args) private string[] Args;
=> await Run(args, []); private Assembly[] AdditionalAssemblies;
public static async Task Run(string[] args, Assembly[] assemblies) // Logging
private ILoggerProvider[] LoggerProviders;
private ILoggerFactory LoggerFactory;
private ILogger<Startup> 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 Args = args;
var providers = LoggerBuildHelper.BuildFromConfiguration(configuration => AdditionalAssemblies = assemblies ?? [];
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()
{ {
configuration.Console.Enable = true;
configuration.Console.EnableAnsiMode = true;
configuration.FileLogging.Enable = false;
});
using var loggerFactory = new LoggerFactory(providers);
var logger = loggerFactory.CreateLogger("Startup");
// Fancy start console output... yes very fancy :> // Fancy start console output... yes very fancy :>
Console.Write("Running "); Console.Write("Running ");
@@ -51,58 +75,132 @@ public class Startup
Console.WriteLine(); Console.WriteLine();
// Building app return Task.CompletedTask;
var builder = WebAssemblyHostBuilder.CreateDefault(args); }
// Load plugins private Task RegisterBase()
var pluginLoader = new PluginLoaderService( {
loggerFactory.CreateLogger<PluginLoaderService>() WebAssemblyHostBuilder.RootComponents.Add<App>("#app");
WebAssemblyHostBuilder.RootComponents.Add<HeadOutlet>("head::after");
WebAssemblyHostBuilder.Services.AddScoped(_ =>
new HttpClient
{
BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress)
}
); );
pluginLoader.AddHttpHostedSource($"{builder.HostEnvironment.BaseAddress}api/pluginsStream"); WebAssemblyHostBuilder.Services.AddScoped<WindowService>();
await pluginLoader.Load(); WebAssemblyHostBuilder.Services.AddMoonCoreBlazorTailwind();
WebAssemblyHostBuilder.Services.AddScoped<LocalStorageService>();
builder.Services.AddSingleton(pluginLoader); WebAssemblyHostBuilder.Services.AutoAddServices<Program>();
// Configure application logging return Task.CompletedTask;
builder.Logging.ClearProviders(); }
builder.Logging.AddProviders(providers);
builder.RootComponents.Add<App>("#app"); private Task RegisterOAuth2()
builder.RootComponents.Add<HeadOutlet>("head::after"); {
WebAssemblyHostBuilder.AddTokenAuthentication();
WebAssemblyHostBuilder.AddOAuth2();
builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); return Task.CompletedTask;
}
builder.AddTokenAuthentication();
builder.AddOAuth2();
builder.Services.AddMoonCoreBlazorTailwind();
builder.Services.AddScoped<WindowService>();
builder.Services.AddScoped<LocalStorageService>();
builder.Services.AutoAddServices<Startup>();
private Task RegisterFormComponents()
{
FormComponentRepository.Set<string, StringComponent>(); FormComponentRepository.Set<string, StringComponent>();
FormComponentRepository.Set<int, IntComponent>(); FormComponentRepository.Set<int, IntComponent>();
FormComponentRepository.Set<DateTime, DateComponent>();
// Interface service return Task.CompletedTask;
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.AddAssembly(typeof(Startup).Assembly);
configuration.AddAssemblies(assemblies); configuration.AddAssemblies(AdditionalAssemblies);
configuration.AddAssemblies(PluginLoaderService.PluginAssemblies);
configuration.AddAssemblies(pluginLoader.PluginAssemblies);
configuration.AddInterface<IAppLoader>(); configuration.AddInterface<IAppLoader>();
configuration.AddInterface<IAppScreen>(); configuration.AddInterface<IAppScreen>();
configuration.AddInterface<ISidebarItemProvider>(); configuration.AddInterface<ISidebarItemProvider>();
}); });
var app = builder.Build(); return Task.CompletedTask;
await app.RunAsync();
} }
#endregion
#region Plugins
private async Task LoadPlugins()
{
// Initialize api server plugin loader
PluginLoaderService = new PluginLoaderService(
LoggerFactory.CreateLogger<PluginLoaderService>()
);
// 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<Startup>();
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
} }

View File

@@ -399,5 +399,14 @@
"whitespace-nowrap", "whitespace-nowrap",
"z-10", "z-10",
"z-40", "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"
] ]