diff --git a/Moonlight.ApiServer.Runtime/Program.cs b/Moonlight.ApiServer.Runtime/Program.cs index a2311356..237a59af 100644 --- a/Moonlight.ApiServer.Runtime/Program.cs +++ b/Moonlight.ApiServer.Runtime/Program.cs @@ -1,5 +1,5 @@ -using Moonlight.ApiServer; -using Moonlight.ApiServer.Runtime; +using Moonlight.ApiServer.Runtime; +using Moonlight.ApiServer.Startup; var pluginLoader = new PluginLoader(); pluginLoader.Initialize(); @@ -8,21 +8,28 @@ await startup.Run(args, pluginLoader.Instances); */ -var cs = new CleanStartup(); +var cs = new Startup(); -var builder = WebApplication.CreateBuilder(); +await cs.Initialize(args, pluginLoader.Instances); -await cs.AddMoonlight(builder, args, pluginLoader.Instances); +var builder = WebApplication.CreateBuilder(args); + +await cs.AddMoonlight(builder); var app = builder.Build(); -await cs.AddMoonlight(app, args, pluginLoader.Instances); +await cs.AddMoonlight(app); -if (app.Environment.IsDevelopment()) - app.UseWebAssemblyDebugging(); +// Handle setup of wasm app hosting in the runtime +// so the Moonlight.ApiServer doesn't need the wasm package +if (cs.Configuration.Frontend.EnableHosting) +{ + if (app.Environment.IsDevelopment()) + app.UseWebAssemblyDebugging(); -app.UseBlazorFrameworkFiles(); -app.UseStaticFiles(); + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); +} await app.RunAsync(); \ No newline at end of file diff --git a/Moonlight.ApiServer/CleanStartup.cs b/Moonlight.ApiServer/CleanStartup.cs deleted file mode 100644 index d3a72d27..00000000 --- a/Moonlight.ApiServer/CleanStartup.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Logging; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer; - -public class CleanStartup -{ - // Logger - private ILogger Logger; - - // Configuration - private AppConfiguration Configuration; - - // WebApplication Stuff - private WebApplication WebApplication; - private WebApplicationBuilder WebApplicationBuilder; - - public async Task AddMoonlight( - WebApplicationBuilder builder, - string[] args, - IPluginStartup[]? plugins = null - ) - { - - } - - public async Task AddMoonlight( - WebApplication application, - string[] args, - IPluginStartup[]? plugins = null - ) - { - } - - #region Misc - - private Task PrintVersion() - { - // Fancy start console output... yes very fancy :> - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - - return Task.CompletedTask; - } - - private Task CreateStorage() - { - Directory.CreateDirectory("storage"); - Directory.CreateDirectory(Path.Combine("storage", "logs")); - - return Task.CompletedTask; - } - - #endregion - - #region Base - - private Task RegisterBase() - { - WebApplicationBuilder.Services.AutoAddServices(); - WebApplicationBuilder.Services.AddHttpClient(); - - WebApplicationBuilder.Services.AddApiExceptionHandler(); - - // Add pre-existing services - WebApplicationBuilder.Services.AddSingleton(Configuration); - - // Configure controllers - var mvcBuilder = WebApplicationBuilder.Services.AddControllers(); - - // Add plugin assemblies as application parts - foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct()) - mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly); - - 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 MapBase() - { - WebApplication.MapControllers(); - - if (Configuration.Client.Enable) - WebApplication.MapFallbackToController("Index", "Frontend"); - - return Task.CompletedTask; - } - - private Task ConfigureKestrel() - { - WebApplicationBuilder.WebHost.ConfigureKestrel(kestrelOptions => - { - var maxUploadInBytes = ByteConverter - .FromMegaBytes(Configuration.Kestrel.UploadLimit) - .Bytes; - - kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes; - }); - - return Task.CompletedTask; - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Configuration/AppConfiguration.cs b/Moonlight.ApiServer/Configuration/AppConfiguration.cs index c6b2ffe5..3691cfb2 100644 --- a/Moonlight.ApiServer/Configuration/AppConfiguration.cs +++ b/Moonlight.ApiServer/Configuration/AppConfiguration.cs @@ -1,24 +1,45 @@ using MoonCore.Helpers; +using YamlDotNet.Serialization; namespace Moonlight.ApiServer.Configuration; -public class AppConfiguration +public record AppConfiguration { + [YamlMember(Description = "The public url your instance should be accessible through")] public string PublicUrl { get; set; } = "http://localhost:5165"; + [YamlMember(Description = "The credentials of the postgres which moonlight should use")] public DatabaseConfig Database { get; set; } = new(); + + [YamlMember(Description = "Settings regarding authentication")] public AuthenticationConfig Authentication { get; set; } = new(); + + [YamlMember(Description = "These options are only meant for development purposes")] public DevelopmentConfig Development { get; set; } = new(); - public ClientConfig Client { get; set; } = new(); + public FrontendData Frontend { get; set; } = new(); public KestrelConfig Kestrel { get; set; } = new(); public MetricsData Metrics { get; set; } = new(); - public class ClientConfig + public static AppConfiguration CreateEmpty() { - public bool Enable { get; set; } = true; + return new AppConfiguration() + { + // Set arrays as empty here + + Kestrel = new() + { + AllowedOrigins = [] + } + }; + } + + public record FrontendData + { + [YamlMember(Description = "Enable the hosting of the frontend. Disable this if you only want to run the api server")] + public bool EnableHosting { get; set; } = true; } - public class DatabaseConfig + public record DatabaseConfig { public string Host { get; set; } = "your-database-host.name"; public int Port { get; set; } = 5432; @@ -29,15 +50,19 @@ public class AppConfiguration public string Database { get; set; } = "db_name"; } - public class AuthenticationConfig + public record AuthenticationConfig { + [YamlMember(Description = "The secret token to use for creating jwts and encrypting things. This needs to be at least 32 characters long")] public string Secret { get; set; } = Formatter.GenerateString(32); + + [YamlMember(Description = "The lifespan of generated user tokens in hours")] public int TokenDuration { get; set; } = 24 * 10; + [YamlMember(Description = "This enables the use of the local oauth2 provider, so moonlight will use itself as an oauth2 provider")] public bool EnableLocalOAuth2 { get; set; } = true; public OAuth2Data OAuth2 { get; set; } = new(); - public class OAuth2Data + public record OAuth2Data { public string Secret { get; set; } = Formatter.GenerateString(32); public string ClientId { get; set; } = Formatter.GenerateString(8); @@ -46,24 +71,32 @@ public class AppConfiguration public string? AccessEndpoint { get; set; } public string? AuthorizationRedirect { get; set; } + [YamlMember(Description = "This specifies if the first registered user will become an admin automatically. This only works when using local oauth2")] public bool FirstUserAdmin { get; set; } = true; } } - public class DevelopmentConfig + public record DevelopmentConfig { + [YamlMember(Description = "This toggles the availability of the api docs via /api/swagger")] public bool EnableApiDocs { get; set; } = false; } - public class KestrelConfig + public record KestrelConfig { + [YamlMember(Description = "The upload limit in megabytes for the api server")] public int UploadLimit { get; set; } = 100; - public string AllowedOrigins { get; set; } = "*"; + + [YamlMember(Description = "The allowed origins for the api server. Use * to allow all origins (which is not advised)")] + public string[] AllowedOrigins { get; set; } = ["*"]; } - public class MetricsData + public record MetricsData { + [YamlMember(Description = "This enables the collecting of metrics and allows access to the /metrics endpoint")] public bool Enable { get; set; } = false; + + [YamlMember(Description = "The interval in which metrics are created, specified in seconds")] public int Interval { get; set; } = 15; } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/ApiKey.cs b/Moonlight.ApiServer/Database/Entities/ApiKey.cs index 3dbfb9c8..9b75d2ec 100644 --- a/Moonlight.ApiServer/Database/Entities/ApiKey.cs +++ b/Moonlight.ApiServer/Database/Entities/ApiKey.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace Moonlight.ApiServer.Database.Entities; +namespace Moonlight.ApiServer.Database.Entities; public class ApiKey { diff --git a/Moonlight.ApiServer/Database/Entities/User.cs b/Moonlight.ApiServer/Database/Entities/User.cs index 0a13fafb..4a09c19d 100644 --- a/Moonlight.ApiServer/Database/Entities/User.cs +++ b/Moonlight.ApiServer/Database/Entities/User.cs @@ -1,6 +1,4 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace Moonlight.ApiServer.Database.Entities; +namespace Moonlight.ApiServer.Database.Entities; public class User { diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs index 1ec69246..f065d97d 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/ThemeController.cs @@ -1,7 +1,6 @@ using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using MoonCore.Helpers; using Moonlight.Shared.Http.Requests.Admin.Sys; namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; diff --git a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs index 096d198c..aee4328d 100644 --- a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs @@ -1,6 +1,5 @@ using System.IdentityModel.Tokens.Jwt; using System.Text; -using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -8,13 +7,11 @@ using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; -using MoonCore.Helpers; using Moonlight.ApiServer.Configuration; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Interfaces; using Moonlight.Shared.Http.Requests.Auth; using Moonlight.Shared.Http.Responses.Auth; -using Moonlight.Shared.Http.Responses.OAuth2; namespace Moonlight.ApiServer.Http.Controllers.Auth; @@ -77,7 +74,7 @@ public class AuthController : Controller // Generate token var securityTokenDescriptor = new SecurityTokenDescriptor() { - Expires = DateTime.Now.AddYears(Configuration.Authentication.TokenDuration), + Expires = DateTime.Now.AddHours(Configuration.Authentication.TokenDuration), IssuedAt = DateTime.Now, NotBefore = DateTime.Now.AddMinutes(-1), Claims = new Dictionary() diff --git a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor b/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor index f9f7adc3..0832de32 100644 --- a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor +++ b/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor @@ -14,8 +14,8 @@ } - - + + diff --git a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs index 12309cf5..54013d68 100644 --- a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs +++ b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs @@ -12,7 +12,6 @@ using Moonlight.ApiServer.Models; using Moonlight.ApiServer.Plugins; using Moonlight.ApiServer.Services; using OpenTelemetry.Metrics; -using OpenTelemetry.Trace; namespace Moonlight.ApiServer.Implementations.Startup; @@ -87,7 +86,7 @@ public class CoreStartup : IPluginStartup #region Client / Frontend - if (configuration.Client.Enable) + if (configuration.Frontend.EnableHosting) { builder.Services.AddSingleton(new FrontendConfigurationOption() { diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index ccec0c63..1e6bec96 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -14,7 +14,7 @@ Moonlight.ApiServer - 2.1.2 + 2.1.3 Moonlight Panel A build of the api server for moonlight development https://github.com/Moonlight-Panel/Moonlight @@ -38,16 +38,6 @@ - - true - src - Never - - - true - src - Never - diff --git a/Moonlight.ApiServer/Services/ApiKeyService.cs b/Moonlight.ApiServer/Services/ApiKeyService.cs index 72411ff5..6910ae71 100644 --- a/Moonlight.ApiServer/Services/ApiKeyService.cs +++ b/Moonlight.ApiServer/Services/ApiKeyService.cs @@ -1,6 +1,5 @@ using System.IdentityModel.Tokens.Jwt; using System.Text; -using System.Text.Json; using Microsoft.IdentityModel.Tokens; using MoonCore.Attributes; using Moonlight.ApiServer.Configuration; diff --git a/Moonlight.ApiServer/Services/FrontendService.cs b/Moonlight.ApiServer/Services/FrontendService.cs index a470ebe6..e295f4bf 100644 --- a/Moonlight.ApiServer/Services/FrontendService.cs +++ b/Moonlight.ApiServer/Services/FrontendService.cs @@ -83,7 +83,7 @@ public class FrontendService public async Task GenerateZip() // TODO: Rework to be able to extract everything successfully { // We only allow the access to this function when we are actually hosting the frontend - if (!Configuration.Client.Enable) + if (!Configuration.Frontend.EnableHosting) throw new HttpApiException("The hosting of the wasm client has been disabled", 400); // Load and check wasm path diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs deleted file mode 100644 index f1d27512..00000000 --- a/Moonlight.ApiServer/Startup.cs +++ /dev/null @@ -1,552 +0,0 @@ -using System.Text; -using System.Text.Json; -using Hangfire; -using Hangfire.EntityFrameworkCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.IdentityModel.Tokens; -using MoonCore.EnvConfiguration; -using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Extensions; -using MoonCore.Extended.Helpers; -using MoonCore.Extended.JwtInvalidation; -using MoonCore.Extensions; -using MoonCore.Helpers; -using MoonCore.Logging; -using MoonCore.Permissions; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Implementations; -using Moonlight.ApiServer.Implementations.Startup; -using Moonlight.ApiServer.Interfaces; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer; - -// Cry about it -#pragma warning disable ASP0000 - -public class StartupX -{ - private string[] Args; - - // Logging - private ILoggerFactory LoggerFactory; - private ILogger Logger; - - // Configuration - private AppConfiguration Configuration; - private IConfigurationRoot ConfigurationRoot; - - // WebApplication Stuff - private WebApplication WebApplication; - private WebApplicationBuilder WebApplicationBuilder; - - // Plugin Loading - private IPluginStartup[] PluginStartups; - private IPluginStartup[] AdditionalPlugins; - private IServiceProvider PluginLoadServiceProvider; - - public async Task Run(string[] args, IPluginStartup[]? additionalPlugins = null) - { - Args = args; - AdditionalPlugins = additionalPlugins ?? []; - - await PrintVersion(); - - await CreateStorage(); - await SetupAppConfiguration(); - await SetupLogging(); - await InitializePlugins(); - - await CreateWebApplicationBuilder(); - - await ConfigureKestrel(); - await RegisterAppConfiguration(); - await RegisterLogging(); - await RegisterBase(); - await RegisterDatabase(); - await RegisterAuth(); - await RegisterCors(); - await RegisterHangfire(); - await HookPluginBuild(); - await RegisterPluginAssets(); - - await BuildWebApplication(); - - await PrepareDatabase(); - - await UseCors(); - await UseBase(); - await UseAuth(); - await UseHangfire(); - await HookPluginConfigure(); - - await MapBase(); - 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") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - - return Task.CompletedTask; - } - - private Task CreateStorage() - { - Directory.CreateDirectory("storage"); - Directory.CreateDirectory(Path.Combine("storage", "logs")); - - 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); - - // Configure controllers - var mvcBuilder = WebApplicationBuilder.Services.AddControllers(); - - // Add plugin assemblies as application parts - foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct()) - mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly); - - 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 MapBase() - { - WebApplication.MapControllers(); - - if (Configuration.Client.Enable) - WebApplication.MapFallbackToController("Index", "Frontend"); - - return Task.CompletedTask; - } - - private Task ConfigureKestrel() - { - WebApplicationBuilder.WebHost.ConfigureKestrel(kestrelOptions => - { - var maxUploadInBytes = ByteConverter - .FromMegaBytes(Configuration.Kestrel.UploadLimit) - .Bytes; - - kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes; - }); - - return Task.CompletedTask; - } - - #endregion - - #region Plugin Loading - - private Task InitializePlugins() - { - // Create service provider for starting up - var serviceCollection = new ServiceCollection(); - - serviceCollection.AddSingleton(Configuration); - - serviceCollection.AddLogging(builder => - { - builder.ClearProviders(); - builder.AddAnsiConsole(); - }); - - PluginLoadServiceProvider = serviceCollection.BuildServiceProvider(); - - // Collect startups - var pluginStartups = new List(); - - pluginStartups.AddRange(AdditionalPlugins); // Used by the development server - - // Do NOT remove the following comment, as its used to place the plugin startup register calls - // MLBUILD_PLUGIN_STARTUP_HERE - - - PluginStartups = pluginStartups.ToArray(); - - return Task.CompletedTask; - } - - private Task RegisterPluginAssets() - { - return Task.CompletedTask; - } - - #region Hooks - - private async Task HookPluginBuild() - { - foreach (var pluginAppStartup in PluginStartups) - { - try - { - await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, 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 PluginStartups) - { - try - { - await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, 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 PluginStartups) - { - try - { - await pluginEndpointStartup.ConfigureEndpoints(PluginLoadServiceProvider, WebApplication); - } - catch (Exception e) - { - Logger.LogError( - "An error occured while processing 'ConfigureEndpoints' for '{name}': {e}", - pluginEndpointStartup.GetType().FullName, - e - ); - } - } - } - - #endregion - - #endregion - - #region Configurations - - private async Task SetupAppConfiguration() - { - // Configure configuration (wow) - var configurationBuilder = new ConfigurationBuilder(); - - // Ensure configuration file exists - var jsonFilePath = Path.Combine(Directory.GetCurrentDirectory(), "storage", "app.json"); - - if (!File.Exists(jsonFilePath)) - await File.WriteAllTextAsync(jsonFilePath, JsonSerializer.Serialize(new AppConfiguration())); - - configurationBuilder.AddJsonFile( - jsonFilePath - ); - - configurationBuilder.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_"); - - ConfigurationRoot = configurationBuilder.Build(); - - // Retrieve configuration - Configuration = ConfigurationRoot.Get()!; - } - - private Task RegisterAppConfiguration() - { - WebApplicationBuilder.Services.AddSingleton(Configuration); - 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 - - private Task SetupLogging() - { - LoggerFactory = new LoggerFactory(); - LoggerFactory.AddAnsiConsole(); - - Logger = LoggerFactory.CreateLogger(); - - return Task.CompletedTask; - } - - private async Task RegisterLogging() - { - // Configure application logging - WebApplicationBuilder.Logging.ClearProviders(); - WebApplicationBuilder.Logging.AddAnsiConsole(); - WebApplicationBuilder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log")); - - // Logging levels - var logConfigPath = Path.Combine("storage", "logConfig.json"); - - // Ensure logging config, add a default one is missing - if (!File.Exists(logConfigPath)) - { - var defaultLogLevels = new Dictionary - { - { "Default", "Information" }, - { "Microsoft.AspNetCore", "Warning" }, - { "System.Net.Http.HttpClient", "Warning" } - }; - - var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels); - await File.WriteAllTextAsync(logConfigPath, logLevelsJson); - } - - // Add logging configuration - var logLevels = JsonSerializer.Deserialize>( - await File.ReadAllTextAsync(logConfigPath) - )!; - - foreach (var level in logLevels) - WebApplicationBuilder.Logging.AddFilter(level.Key, Enum.Parse(level.Value)); - - // Mute exception handler middleware - // https://github.com/dotnet/aspnetcore/issues/19740 - WebApplicationBuilder.Logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", - LogLevel.Critical - ); - - WebApplicationBuilder.Logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware", - LogLevel.Critical - ); - } - - #endregion - - #region Database - - private Task RegisterDatabase() - { - WebApplicationBuilder.Services.AddDatabaseMappings(); - WebApplicationBuilder.Services.AddServiceCollectionAccessor(); - - WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>)); - - return Task.CompletedTask; - } - - private async Task PrepareDatabase() - { - await WebApplication.Services.EnsureDatabaseMigrated(); - - WebApplication.Services.GenerateDatabaseMappings(); - } - - #endregion - - #region Authentication & Authorisation - - private Task RegisterAuth() - { - WebApplicationBuilder.Services - .AddAuthentication("coreAuthentication") - .AddJwtBearer("coreAuthentication", options => - { - options.TokenValidationParameters = new() - { - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( - Configuration.Authentication.Secret - )), - ValidateIssuerSigningKey = true, - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero, - ValidateAudience = true, - ValidAudience = Configuration.PublicUrl, - ValidateIssuer = true, - ValidIssuer = Configuration.PublicUrl - }; - }); - - WebApplicationBuilder.Services.AddJwtBearerInvalidation("coreAuthentication"); - WebApplicationBuilder.Services.AddScoped(); - - WebApplicationBuilder.Services.AddAuthorization(); - - WebApplicationBuilder.Services.AddAuthorizationPermissions(options => - { - options.ClaimName = "permissions"; - options.Prefix = "permissions:"; - }); - - // Add local oauth2 provider if enabled - if (Configuration.Authentication.EnableLocalOAuth2) - WebApplicationBuilder.Services.AddScoped(); - - return Task.CompletedTask; - } - - private Task UseAuth() - { - WebApplication.UseAuthentication(); - - WebApplication.UseAuthorization(); - - return Task.CompletedTask; - } - - #endregion - - #region Cors - - private Task RegisterCors() - { - var allowedOrigins = Configuration.Kestrel.AllowedOrigins.Split(";", StringSplitOptions.RemoveEmptyEntries); - - WebApplicationBuilder.Services.AddCors(options => - { - var cors = new CorsPolicyBuilder(); - - if (allowedOrigins.Contains("*")) - { - cors.SetIsOriginAllowed(_ => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials(); - } - else - { - cors.WithOrigins(allowedOrigins) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); - } - - options.AddDefaultPolicy( - cors.Build() - ); - }); - - return Task.CompletedTask; - } - - private Task UseCors() - { - WebApplication.UseCors(); - - return Task.CompletedTask; - } - - #endregion - - #region Hangfire - - private Task RegisterHangfire() - { - WebApplicationBuilder.Services.AddHangfire((provider, configuration) => - { - configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180); - configuration.UseSimpleAssemblyNameTypeSerializer(); - configuration.UseRecommendedSerializerSettings(); - configuration.UseEFCoreStorage(() => - { - var scope = provider.CreateScope(); - return scope.ServiceProvider.GetRequiredService(); - }, new EFCoreStorageOptions()); - }); - - WebApplicationBuilder.Services.AddHangfireServer(); - - WebApplicationBuilder.Logging.AddFilter( - "Hangfire.Server.BackgroundServerProcess", - LogLevel.Warning - ); - - WebApplicationBuilder.Logging.AddFilter( - "Hangfire.BackgroundJobServer", - LogLevel.Warning - ); - - return Task.CompletedTask; - } - - private Task UseHangfire() - { - if (WebApplication.Environment.IsDevelopment()) - WebApplication.UseHangfireDashboard(); - - return Task.CompletedTask; - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/CleanStartup.cs b/Moonlight.ApiServer/Startup/CleanStartup.cs deleted file mode 100644 index 037922da..00000000 --- a/Moonlight.ApiServer/Startup/CleanStartup.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer.Startup; - -public partial class CleanStartup -{ - // Logger - private ILogger Logger; - - // Configuration - private AppConfiguration Configuration; - - // WebApplication Stuff - private WebApplication WebApplication; - private WebApplicationBuilder WebApplicationBuilder; - - public async Task AddMoonlight( - WebApplicationBuilder builder, - string[] args, - IPluginStartup[]? plugins = null - ) - { - - } - - public async Task AddMoonlight( - WebApplication application, - string[] args, - IPluginStartup[]? plugins = null - ) - { - } - - #region Misc - - private Task PrintVersion() - { - // Fancy start console output... yes very fancy :> - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - - return Task.CompletedTask; - } - - private Task CreateStorage() - { - Directory.CreateDirectory("storage"); - Directory.CreateDirectory(Path.Combine("storage", "logs")); - - return Task.CompletedTask; - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Auth.cs b/Moonlight.ApiServer/Startup/Startup.Auth.cs index adb440e4..593ee009 100644 --- a/Moonlight.ApiServer/Startup/Startup.Auth.cs +++ b/Moonlight.ApiServer/Startup/Startup.Auth.cs @@ -1,6 +1,61 @@ +using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using MoonCore.Extended.JwtInvalidation; +using MoonCore.Permissions; +using Moonlight.ApiServer.Implementations; +using Moonlight.ApiServer.Interfaces; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - + private Task RegisterAuth() + { + WebApplicationBuilder.Services + .AddAuthentication("coreAuthentication") + .AddJwtBearer("coreAuthentication", options => + { + options.TokenValidationParameters = new() + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( + Configuration.Authentication.Secret + )), + ValidateIssuerSigningKey = true, + ValidateLifetime = true, + ClockSkew = TimeSpan.Zero, + ValidateAudience = true, + ValidAudience = Configuration.PublicUrl, + ValidateIssuer = true, + ValidIssuer = Configuration.PublicUrl + }; + }); + + WebApplicationBuilder.Services.AddJwtBearerInvalidation("coreAuthentication"); + WebApplicationBuilder.Services.AddScoped(); + + WebApplicationBuilder.Services.AddAuthorization(); + + WebApplicationBuilder.Services.AddAuthorizationPermissions(options => + { + options.ClaimName = "permissions"; + options.Prefix = "permissions:"; + }); + + // Add local oauth2 provider if enabled + if (Configuration.Authentication.EnableLocalOAuth2) + WebApplicationBuilder.Services.AddScoped(); + + return Task.CompletedTask; + } + + private Task UseAuth() + { + WebApplication.UseAuthentication(); + + WebApplication.UseAuthorization(); + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Base.cs b/Moonlight.ApiServer/Startup/Startup.Base.cs index 3fcb9a0b..6ee388e2 100644 --- a/Moonlight.ApiServer/Startup/Startup.Base.cs +++ b/Moonlight.ApiServer/Startup/Startup.Base.cs @@ -1,19 +1,17 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using MoonCore.Extended.Extensions; using MoonCore.Extensions; using MoonCore.Helpers; -using Moonlight.ApiServer.Plugins; namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - private Task RegisterBase(IPluginStartup[] pluginStartups) + private Task RegisterBase() { - WebApplicationBuilder.Services.AutoAddServices(); + WebApplicationBuilder.Services.AutoAddServices(); WebApplicationBuilder.Services.AddHttpClient(); WebApplicationBuilder.Services.AddApiExceptionHandler(); @@ -25,7 +23,7 @@ public partial class CleanStartup var mvcBuilder = WebApplicationBuilder.Services.AddControllers(); // Add plugin assemblies as application parts - foreach (var pluginStartup in pluginStartups.Select(x => x.GetType().Assembly).Distinct()) + foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct()) mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly); return Task.CompletedTask; @@ -36,15 +34,6 @@ public partial class CleanStartup WebApplication.UseRouting(); WebApplication.UseExceptionHandler(); - if (Configuration.Client.Enable) - { - if (WebApplication.Environment.IsDevelopment()) - WebApplication.UseWebAssemblyDebugging(); - - WebApplication.UseBlazorFrameworkFiles(); - WebApplication.UseStaticFiles(); - } - return Task.CompletedTask; } @@ -52,7 +41,7 @@ public partial class CleanStartup { WebApplication.MapControllers(); - if (Configuration.Client.Enable) + if (Configuration.Frontend.EnableHosting) WebApplication.MapFallbackToController("Index", "Frontend"); return Task.CompletedTask; diff --git a/Moonlight.ApiServer/Startup/Startup.Config.cs b/Moonlight.ApiServer/Startup/Startup.Config.cs index adb440e4..1ec38500 100644 --- a/Moonlight.ApiServer/Startup/Startup.Config.cs +++ b/Moonlight.ApiServer/Startup/Startup.Config.cs @@ -1,6 +1,35 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using MoonCore.EnvConfiguration; +using MoonCore.Yaml; +using Moonlight.ApiServer.Configuration; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - + private async Task SetupAppConfiguration() + { + var configPath = Path.Combine("storage", "config.yml"); + + await YamlDefaultGenerator.Generate(configPath); + + // Configure configuration (wow) + var configurationBuilder = new ConfigurationBuilder(); + + configurationBuilder.AddYamlFile(configPath); + configurationBuilder.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_"); + + var configurationRoot = configurationBuilder.Build(); + + // Retrieve configuration + Configuration = AppConfiguration.CreateEmpty(); + configurationRoot.Bind(Configuration); + } + + private Task RegisterAppConfiguration() + { + WebApplicationBuilder.Services.AddSingleton(Configuration); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Database.cs b/Moonlight.ApiServer/Startup/Startup.Database.cs index adb440e4..5f5dcb51 100644 --- a/Moonlight.ApiServer/Startup/Startup.Database.cs +++ b/Moonlight.ApiServer/Startup/Startup.Database.cs @@ -1,6 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using MoonCore.Extended.Abstractions; +using MoonCore.Extended.Extensions; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - + private Task RegisterDatabase() + { + WebApplicationBuilder.Services.AddDatabaseMappings(); + WebApplicationBuilder.Services.AddServiceCollectionAccessor(); + + WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>)); + + return Task.CompletedTask; + } + + private async Task PrepareDatabase() + { + await WebApplication.Services.EnsureDatabaseMigrated(); + + WebApplication.Services.GenerateDatabaseMappings(); + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Hangfire.cs b/Moonlight.ApiServer/Startup/Startup.Hangfire.cs index adb440e4..b06583f6 100644 --- a/Moonlight.ApiServer/Startup/Startup.Hangfire.cs +++ b/Moonlight.ApiServer/Startup/Startup.Hangfire.cs @@ -1,6 +1,48 @@ +using Hangfire; +using Hangfire.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Moonlight.ApiServer.Database; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - + private Task RegisterHangfire() + { + WebApplicationBuilder.Services.AddHangfire((provider, configuration) => + { + configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180); + configuration.UseSimpleAssemblyNameTypeSerializer(); + configuration.UseRecommendedSerializerSettings(); + configuration.UseEFCoreStorage(() => + { + var scope = provider.CreateScope(); + return scope.ServiceProvider.GetRequiredService(); + }, new EFCoreStorageOptions()); + }); + + WebApplicationBuilder.Services.AddHangfireServer(); + + WebApplicationBuilder.Logging.AddFilter( + "Hangfire.Server.BackgroundServerProcess", + LogLevel.Warning + ); + + WebApplicationBuilder.Logging.AddFilter( + "Hangfire.BackgroundJobServer", + LogLevel.Warning + ); + + return Task.CompletedTask; + } + + private Task UseHangfire() + { + if (WebApplication.Environment.IsDevelopment()) + WebApplication.UseHangfireDashboard(); + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Logging.cs b/Moonlight.ApiServer/Startup/Startup.Logging.cs index adb440e4..49411c03 100644 --- a/Moonlight.ApiServer/Startup/Startup.Logging.cs +++ b/Moonlight.ApiServer/Startup/Startup.Logging.cs @@ -1,6 +1,63 @@ +using System.Text.Json; +using Microsoft.Extensions.Logging; +using MoonCore.Logging; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { - + private Task SetupLogging() + { + var loggerFactory = new LoggerFactory(); + loggerFactory.AddAnsiConsole(); + + Logger = loggerFactory.CreateLogger(); + + return Task.CompletedTask; + } + + private async Task RegisterLogging() + { + // Configure application logging + WebApplicationBuilder.Logging.ClearProviders(); + WebApplicationBuilder.Logging.AddAnsiConsole(); + WebApplicationBuilder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log")); + + // Logging levels + var logConfigPath = Path.Combine("storage", "logConfig.json"); + + // Ensure logging config, add a default one is missing + if (!File.Exists(logConfigPath)) + { + var defaultLogLevels = new Dictionary + { + { "Default", "Information" }, + { "Microsoft.AspNetCore", "Warning" }, + { "System.Net.Http.HttpClient", "Warning" } + }; + + var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels); + await File.WriteAllTextAsync(logConfigPath, logLevelsJson); + } + + // Add logging configuration + var logLevels = JsonSerializer.Deserialize>( + await File.ReadAllTextAsync(logConfigPath) + )!; + + foreach (var level in logLevels) + WebApplicationBuilder.Logging.AddFilter(level.Key, Enum.Parse(level.Value)); + + // Mute exception handler middleware + // https://github.com/dotnet/aspnetcore/issues/19740 + WebApplicationBuilder.Logging.AddFilter( + "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", + LogLevel.Critical + ); + + WebApplicationBuilder.Logging.AddFilter( + "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware", + LogLevel.Critical + ); + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Misc.cs b/Moonlight.ApiServer/Startup/Startup.Misc.cs index adb440e4..f8392f05 100644 --- a/Moonlight.ApiServer/Startup/Startup.Misc.cs +++ b/Moonlight.ApiServer/Startup/Startup.Misc.cs @@ -1,6 +1,73 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { + private Task PrintVersion() + { + // Fancy start console output... yes very fancy :> + var rainbow = new Crayon.Rainbow(0.5); + foreach (var c in "Moonlight") + { + Console.Write( + rainbow + .Next() + .Bold() + .Text(c.ToString()) + ); + } + + Console.WriteLine(); + + return Task.CompletedTask; + } + + private Task CreateStorage() + { + Directory.CreateDirectory("storage"); + Directory.CreateDirectory(Path.Combine("storage", "logs")); + + return Task.CompletedTask; + } + private Task RegisterCors() + { + var allowedOrigins = Configuration.Kestrel.AllowedOrigins; + + WebApplicationBuilder.Services.AddCors(options => + { + var cors = new CorsPolicyBuilder(); + + if (allowedOrigins.Contains("*")) + { + cors.SetIsOriginAllowed(_ => true) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + } + else + { + cors.WithOrigins(allowedOrigins) + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + } + + options.AddDefaultPolicy( + cors.Build() + ); + }); + + return Task.CompletedTask; + } + + private Task UseCors() + { + WebApplication.UseCors(); + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Plugins.cs b/Moonlight.ApiServer/Startup/Startup.Plugins.cs index a2342ac8..ec01272c 100644 --- a/Moonlight.ApiServer/Startup/Startup.Plugins.cs +++ b/Moonlight.ApiServer/Startup/Startup.Plugins.cs @@ -5,12 +5,12 @@ using Moonlight.ApiServer.Plugins; namespace Moonlight.ApiServer.Startup; -public partial class CleanStartup +public partial class Startup { private IServiceProvider PluginLoadServiceProvider; private IPluginStartup[] PluginStartups; - private Task InitializePlugins(IPluginStartup[] pluginStartups) + private Task InitializePlugins() { // Create service provider for starting up var serviceCollection = new ServiceCollection(); @@ -24,8 +24,6 @@ public partial class CleanStartup }); PluginLoadServiceProvider = serviceCollection.BuildServiceProvider(); - - PluginStartups = pluginStartups; return Task.CompletedTask; } diff --git a/Moonlight.ApiServer/Startup/Startup.cs b/Moonlight.ApiServer/Startup/Startup.cs new file mode 100644 index 00000000..731ca06b --- /dev/null +++ b/Moonlight.ApiServer/Startup/Startup.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Logging; +using Moonlight.ApiServer.Configuration; +using Moonlight.ApiServer.Plugins; + +namespace Moonlight.ApiServer.Startup; + +public partial class Startup +{ + private string[] Args; + + // Logger + public ILogger Logger { get; private set; } + + // Configuration + public AppConfiguration Configuration { get; private set; } + + // WebApplication Stuff + public WebApplication WebApplication { get; private set; } + public WebApplicationBuilder WebApplicationBuilder { get; private set; } + + public Task Initialize(string[] args, IPluginStartup[]? plugins = null) + { + Args = args; + PluginStartups = plugins ?? []; + + return Task.CompletedTask; + } + + public async Task AddMoonlight(WebApplicationBuilder builder) + { + WebApplicationBuilder = builder; + + await PrintVersion(); + + await CreateStorage(); + await SetupAppConfiguration(); + await SetupLogging(); + await InitializePlugins(); + + await ConfigureKestrel(); + await RegisterAppConfiguration(); + await RegisterLogging(); + await RegisterBase(); + await RegisterDatabase(); + await RegisterAuth(); + await RegisterCors(); + await RegisterHangfire(); + await HookPluginBuild(); + } + + public async Task AddMoonlight(WebApplication application) + { + WebApplication = application; + + await PrepareDatabase(); + + await UseCors(); + await UseBase(); + await UseAuth(); + await UseHangfire(); + await HookPluginConfigure(); + + await MapBase(); + await HookPluginEndpoints(); + } +} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj b/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj index 34411a77..627ac2ce 100644 --- a/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj +++ b/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj @@ -64,6 +64,7 @@ <_ContentIncludedByDefault Remove="wwwroot\js\moonCore.js" /> <_ContentIncludedByDefault Remove="wwwroot\js\moonlight.js" /> + <_ContentIncludedByDefault Remove="wwwroot\svg\logo.svg" /> diff --git a/Moonlight.Client.Runtime/Program.cs b/Moonlight.Client.Runtime/Program.cs index 5cb70381..16b1f2f4 100644 --- a/Moonlight.Client.Runtime/Program.cs +++ b/Moonlight.Client.Runtime/Program.cs @@ -1,9 +1,20 @@ -using Moonlight.Client; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Moonlight.Client.Runtime; - -var startup = new Startup(); +using Moonlight.Client.Startup; var pluginLoader = new PluginLoader(); pluginLoader.Initialize(); -await startup.Run(args, pluginLoader.Instances); \ No newline at end of file +var startup = new Startup(); + +await startup.Initialize(pluginLoader.Instances); + +var wasmHostBuilder = WebAssemblyHostBuilder.CreateDefault(args); + +await startup.AddMoonlight(wasmHostBuilder); + +var wasmApp = wasmHostBuilder.Build(); + +await startup.AddMoonlight(wasmApp); + +await wasmApp.RunAsync(); \ No newline at end of file diff --git a/Moonlight.Client.Runtime/wwwroot/frontend.example.json b/Moonlight.Client.Runtime/wwwroot/frontend.example.json deleted file mode 100644 index aba7d0b1..00000000 --- a/Moonlight.Client.Runtime/wwwroot/frontend.example.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "apiUrl": "http://localhost:5165", - "hostEnvironment": "Static", - "theme": { - "variables": { - } - }, - "scripts": [ - ], - "plugins": { - "assemblies": [ - ], - "entrypoints": [ - ] - } -} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/wwwroot/img/icon-192.png b/Moonlight.Client.Runtime/wwwroot/img/icon-192.png deleted file mode 100644 index 1b417d98..00000000 Binary files a/Moonlight.Client.Runtime/wwwroot/img/icon-192.png and /dev/null differ diff --git a/Moonlight.Client.Runtime/wwwroot/img/icon-512.png b/Moonlight.Client.Runtime/wwwroot/img/icon-512.png deleted file mode 100644 index 437487cf..00000000 Binary files a/Moonlight.Client.Runtime/wwwroot/img/icon-512.png and /dev/null differ diff --git a/Moonlight.Client.Runtime/wwwroot/img/pfp_placeholder.png b/Moonlight.Client.Runtime/wwwroot/img/pfp_placeholder.png deleted file mode 100644 index 1108ddbb..00000000 Binary files a/Moonlight.Client.Runtime/wwwroot/img/pfp_placeholder.png and /dev/null differ diff --git a/Moonlight.Client/Startup.cs b/Moonlight.Client/Startup.cs deleted file mode 100644 index 2963ee95..00000000 --- a/Moonlight.Client/Startup.cs +++ /dev/null @@ -1,310 +0,0 @@ -using System.Text.Json; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Blazor.FlyonUi; -using MoonCore.Blazor.FlyonUi.Auth; -using MoonCore.Blazor.Services; -using MoonCore.Extensions; -using MoonCore.Helpers; -using MoonCore.Logging; -using MoonCore.Permissions; -using Moonlight.Client.Plugins; -using Moonlight.Client.Services; -using Moonlight.Shared.Misc; -using Moonlight.Client.UI; -using WindowService = Moonlight.Client.Services.WindowService; - -namespace Moonlight.Client; - -public class Startup -{ - private string[] Args; - - // Configuration - private FrontendConfiguration Configuration; - - // Logging - private ILoggerFactory LoggerFactory; - private ILogger Logger; - - // WebAssemblyHost - private WebAssemblyHostBuilder WebAssemblyHostBuilder; - private WebAssemblyHost WebAssemblyHost; - - // Plugin Loading - private IPluginStartup[] AdditionalPlugins; - private IPluginStartup[] PluginStartups; - private IServiceProvider PluginLoadServiceProvider; - - public async Task Run(string[] args, IPluginStartup[]? additionalPlugins = null) - { - Args = args; - AdditionalPlugins = additionalPlugins ?? []; - - await PrintVersion(); - await SetupLogging(); - - await CreateWebAssemblyHostBuilder(); - - await LoadConfiguration(); - await InitializePlugins(); - - await RegisterLogging(); - await RegisterBase(); - await RegisterAuthentication(); - await HookPluginBuild(); - - await BuildWebAssemblyHost(); - - await HookPluginConfigure(); - await LoadAssets(); - - await WebAssemblyHost.RunAsync(); - } - - private Task PrintVersion() - { - // Fancy start console output... yes very fancy :> - Console.Write("Running "); - - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - - return Task.CompletedTask; - } - - private async Task LoadConfiguration() - { - try - { - using var httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress); - - var jsonText = await httpClient.GetStringAsync("frontend.json"); - - Configuration = JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true - })!; - - WebAssemblyHostBuilder.Services.AddSingleton(Configuration); - } - catch (Exception e) - { - Logger.LogCritical("Unable to load configuration. Unable to continue: {e}", e); - throw; - } - } - - private Task RegisterBase() - { - WebAssemblyHostBuilder.RootComponents.Add("#app"); - WebAssemblyHostBuilder.RootComponents.Add("head::after"); - - WebAssemblyHostBuilder.Services.AddScoped(_ => - new HttpClient - { - BaseAddress = new Uri(Configuration.ApiUrl) - } - ); - - WebAssemblyHostBuilder.Services.AddScoped(sp => - { - var httpClient = sp.GetRequiredService(); - var httpApiClient = new HttpApiClient(httpClient); - - var localStorageService = sp.GetRequiredService(); - - httpApiClient.OnConfigureRequest += async request => - { - var accessToken = await localStorageService.GetString("AccessToken"); - - if (string.IsNullOrEmpty(accessToken)) - return; - - request.Headers.Add("Authorization", $"Bearer {accessToken}"); - }; - - return httpApiClient; - }); - - WebAssemblyHostBuilder.Services.AddScoped(); - WebAssemblyHostBuilder.Services.AddFileManagerOperations(); - WebAssemblyHostBuilder.Services.AddFlyonUiServices(); - WebAssemblyHostBuilder.Services.AddScoped(); - - WebAssemblyHostBuilder.Services.AddScoped(); - - WebAssemblyHostBuilder.Services.AutoAddServices(); - - return Task.CompletedTask; - } - - #region Asset Loading - - private async Task LoadAssets() - { - var jsRuntime = WebAssemblyHost.Services.GetRequiredService(); - - foreach (var scriptName in Configuration.Scripts) - await jsRuntime.InvokeVoidAsync("moonlight.assets.loadJavascript", scriptName); - - foreach (var styleName in Configuration.Styles) - await jsRuntime.InvokeVoidAsync("moonlight.assets.loadStylesheet", styleName); - } - - #endregion - - #region Plugins - - private Task InitializePlugins() - { - // Define minimal service collection - var startupSc = new ServiceCollection(); - - // Create logging proxy - startupSc.AddLogging(builder => - { - builder.ClearProviders(); - builder.AddAnsiConsole(); - }); - - PluginLoadServiceProvider = startupSc.BuildServiceProvider(); - - // Collect startups - var pluginStartups = new List(); - - pluginStartups.AddRange(AdditionalPlugins); // Used by the development server - - // Do NOT remove the following comment, as its used to place the plugin startup register calls - // MLBUILD_PLUGIN_STARTUP_HERE - - - PluginStartups = pluginStartups.ToArray(); - - // Add application assembly service - var appAssemblyService = new ApplicationAssemblyService(); - - appAssemblyService.Assemblies.AddRange( - PluginStartups - .Select(x => x.GetType().Assembly) - .Distinct() - ); - - WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService); - - return Task.CompletedTask; - } - - #region Hooks - - private async Task HookPluginBuild() - { - foreach (var pluginAppStartup in PluginStartups) - { - try - { - await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebAssemblyHostBuilder); - } - 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 PluginStartups) - { - try - { - await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebAssemblyHost); - } - catch (Exception e) - { - Logger.LogError( - "An error occured while processing 'ConfigureApp' for '{name}': {e}", - pluginAppStartup.GetType().FullName, - e - ); - } - } - } - - #endregion - - #endregion - - #region Logging - - private Task SetupLogging() - { - LoggerFactory = new LoggerFactory(); - LoggerFactory.AddAnsiConsole(); - - Logger = LoggerFactory.CreateLogger(); - - return Task.CompletedTask; - } - - private Task RegisterLogging() - { - WebAssemblyHostBuilder.Logging.ClearProviders(); - WebAssemblyHostBuilder.Logging.AddAnsiConsole(); - - 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 - - #region Authentication - - private Task RegisterAuthentication() - { - WebAssemblyHostBuilder.Services.AddAuthorizationCore(); - WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState(); - - WebAssemblyHostBuilder.Services.AddAuthenticationStateManager(); - - WebAssemblyHostBuilder.Services.AddAuthorizationPermissions(options => - { - options.ClaimName = "permissions"; - options.Prefix = "permissions:"; - }); - - return Task.CompletedTask; - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Auth.cs b/Moonlight.Client/Startup/Startup.Auth.cs new file mode 100644 index 00000000..f5677ea1 --- /dev/null +++ b/Moonlight.Client/Startup/Startup.Auth.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using MoonCore.Blazor.FlyonUi.Auth; +using MoonCore.Permissions; +using Moonlight.Client.Services; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + private Task RegisterAuthentication() + { + WebAssemblyHostBuilder.Services.AddAuthorizationCore(); + WebAssemblyHostBuilder.Services.AddCascadingAuthenticationState(); + + WebAssemblyHostBuilder.Services.AddAuthenticationStateManager(); + + WebAssemblyHostBuilder.Services.AddAuthorizationPermissions(options => + { + options.ClaimName = "permissions"; + options.Prefix = "permissions:"; + }); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Base.cs b/Moonlight.Client/Startup/Startup.Base.cs new file mode 100644 index 00000000..447c64b4 --- /dev/null +++ b/Moonlight.Client/Startup/Startup.Base.cs @@ -0,0 +1,56 @@ +using Microsoft.Extensions.DependencyInjection; +using MoonCore.Blazor.FlyonUi; +using MoonCore.Blazor.Services; +using MoonCore.Extensions; +using MoonCore.Helpers; +using Moonlight.Client.Services; +using Moonlight.Client.UI; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + private Task RegisterBase() + { + WebAssemblyHostBuilder.RootComponents.Add("#app"); + WebAssemblyHostBuilder.RootComponents.Add("head::after"); + + WebAssemblyHostBuilder.Services.AddScoped(_ => + new HttpClient + { + BaseAddress = new Uri(Configuration.ApiUrl) + } + ); + + WebAssemblyHostBuilder.Services.AddScoped(sp => + { + var httpClient = sp.GetRequiredService(); + var httpApiClient = new HttpApiClient(httpClient); + + var localStorageService = sp.GetRequiredService(); + + httpApiClient.OnConfigureRequest += async request => + { + var accessToken = await localStorageService.GetString("AccessToken"); + + if (string.IsNullOrEmpty(accessToken)) + return; + + request.Headers.Add("Authorization", $"Bearer {accessToken}"); + }; + + return httpApiClient; + }); + + WebAssemblyHostBuilder.Services.AddScoped(); + WebAssemblyHostBuilder.Services.AddFileManagerOperations(); + WebAssemblyHostBuilder.Services.AddFlyonUiServices(); + WebAssemblyHostBuilder.Services.AddScoped(); + + WebAssemblyHostBuilder.Services.AddScoped(); + + WebAssemblyHostBuilder.Services.AutoAddServices(); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Logging.cs b/Moonlight.Client/Startup/Startup.Logging.cs new file mode 100644 index 00000000..672b0ed5 --- /dev/null +++ b/Moonlight.Client/Startup/Startup.Logging.cs @@ -0,0 +1,24 @@ +using MoonCore.Logging; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + private Task SetupLogging() + { + var loggerFactory = new LoggerFactory(); + loggerFactory.AddAnsiConsole(); + + Logger = loggerFactory.CreateLogger(); + + return Task.CompletedTask; + } + + private Task RegisterLogging() + { + WebAssemblyHostBuilder.Logging.ClearProviders(); + WebAssemblyHostBuilder.Logging.AddAnsiConsole(); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Misc.cs b/Moonlight.Client/Startup/Startup.Misc.cs new file mode 100644 index 00000000..38b3bd27 --- /dev/null +++ b/Moonlight.Client/Startup/Startup.Misc.cs @@ -0,0 +1,52 @@ +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; +using Moonlight.Shared.Misc; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + private Task PrintVersion() + { + // Fancy start console output... yes very fancy :> + Console.Write("Running "); + + var rainbow = new Crayon.Rainbow(0.5); + foreach (var c in "Moonlight") + { + Console.Write( + rainbow + .Next() + .Bold() + .Text(c.ToString()) + ); + } + + Console.WriteLine(); + + return Task.CompletedTask; + } + + private async Task LoadConfiguration() + { + try + { + using var httpClient = new HttpClient(); + httpClient.BaseAddress = new Uri(WebAssemblyHostBuilder.HostEnvironment.BaseAddress); + + var jsonText = await httpClient.GetStringAsync("frontend.json"); + + Configuration = JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + })!; + + WebAssemblyHostBuilder.Services.AddSingleton(Configuration); + } + catch (Exception e) + { + Logger.LogCritical("Unable to load configuration. Unable to continue: {e}", e); + throw; + } + } +} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Plugins.cs b/Moonlight.Client/Startup/Startup.Plugins.cs new file mode 100644 index 00000000..2b97e7eb --- /dev/null +++ b/Moonlight.Client/Startup/Startup.Plugins.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.DependencyInjection; +using MoonCore.Logging; +using Moonlight.Client.Plugins; +using Moonlight.Client.Services; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + private IPluginStartup[] PluginStartups; + private IServiceProvider PluginLoadServiceProvider; + + private Task InitializePlugins() + { + // Define minimal service collection + var startupSc = new ServiceCollection(); + + // Create logging proxy + startupSc.AddLogging(builder => + { + builder.ClearProviders(); + builder.AddAnsiConsole(); + }); + + PluginLoadServiceProvider = startupSc.BuildServiceProvider(); + + // Add application assembly service + var appAssemblyService = new ApplicationAssemblyService(); + + appAssemblyService.Assemblies.AddRange( + PluginStartups + .Select(x => x.GetType().Assembly) + .Distinct() + ); + + WebAssemblyHostBuilder.Services.AddSingleton(appAssemblyService); + + return Task.CompletedTask; + } + + private async Task HookPluginBuild() + { + foreach (var pluginAppStartup in PluginStartups) + { + try + { + await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebAssemblyHostBuilder); + } + 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 PluginStartups) + { + try + { + await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebAssemblyHost); + } + catch (Exception e) + { + Logger.LogError( + "An error occured while processing 'ConfigureApp' for '{name}': {e}", + pluginAppStartup.GetType().FullName, + e + ); + } + } + } +} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.cs b/Moonlight.Client/Startup/Startup.cs new file mode 100644 index 00000000..1d568b6b --- /dev/null +++ b/Moonlight.Client/Startup/Startup.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Moonlight.Client.Plugins; +using Moonlight.Shared.Misc; + +namespace Moonlight.Client.Startup; + +public partial class Startup +{ + public ILogger Logger { get; private set; } + + // WebAssemblyHost + public WebAssemblyHostBuilder WebAssemblyHostBuilder { get; private set; } + public WebAssemblyHost WebAssemblyHost { get; private set; } + + // Configuration + public FrontendConfiguration Configuration { get; private set; } + + + public Task Initialize(IPluginStartup[]? plugins = null) + { + PluginStartups = plugins ?? []; + + return Task.CompletedTask; + } + + public async Task AddMoonlight(WebAssemblyHostBuilder builder) + { + WebAssemblyHostBuilder = builder; + + await PrintVersion(); + await SetupLogging(); + + await LoadConfiguration(); + await InitializePlugins(); + + await RegisterLogging(); + await RegisterBase(); + await RegisterAuthentication(); + await HookPluginBuild(); + } + + public async Task AddMoonlight(WebAssemblyHost assemblyHost) + { + WebAssemblyHost = assemblyHost; + + await HookPluginConfigure(); + } +} \ No newline at end of file diff --git a/Moonlight.Client/UI/Partials/AppHeader.razor b/Moonlight.Client/UI/Partials/AppHeader.razor index 7cf6266f..883d9879 100644 --- a/Moonlight.Client/UI/Partials/AppHeader.razor +++ b/Moonlight.Client/UI/Partials/AppHeader.razor @@ -23,7 +23,7 @@ class="inline-grid shrink-0 align-middle"> diff --git a/Moonlight.Client/UI/Partials/AppSidebar.razor b/Moonlight.Client/UI/Partials/AppSidebar.razor index 815cf189..428b25a1 100644 --- a/Moonlight.Client/UI/Partials/AppSidebar.razor +++ b/Moonlight.Client/UI/Partials/AppSidebar.razor @@ -24,7 +24,7 @@ Moonlight v2.1 @@ -85,7 +85,7 @@
@@ -118,7 +118,7 @@
+ class="h-8 rounded-full" src="/_content/Moonlight.Client/svg/logo.svg" alt=""/>
Moonlight v2.1
diff --git a/Moonlight.Client/UI/Views/Admin/Api/Create.razor b/Moonlight.Client/UI/Views/Admin/Api/Create.razor index ee5d89fa..ae66d577 100644 --- a/Moonlight.Client/UI/Views/Admin/Api/Create.razor +++ b/Moonlight.Client/UI/Views/Admin/Api/Create.razor @@ -1,6 +1,4 @@ @page "/admin/api/create" - -@using System.Text.Json @using MoonCore.Helpers @using Moonlight.Shared.Http.Requests.Admin.ApiKeys @using Moonlight.Shared.Http.Responses.Admin.ApiKeys diff --git a/Moonlight.Client/UI/Views/Admin/Api/Update.razor b/Moonlight.Client/UI/Views/Admin/Api/Update.razor index 92f1c8e9..27ba1786 100644 --- a/Moonlight.Client/UI/Views/Admin/Api/Update.razor +++ b/Moonlight.Client/UI/Views/Admin/Api/Update.razor @@ -1,6 +1,4 @@ @page "/admin/api/{Id:int}" - -@using System.Text.Json @using MoonCore.Helpers @using Moonlight.Shared.Http.Requests.Admin.ApiKeys @using Moonlight.Shared.Http.Responses.Admin.ApiKeys diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Files.razor b/Moonlight.Client/UI/Views/Admin/Sys/Files.razor index cce5691a..436a6d78 100644 --- a/Moonlight.Client/UI/Views/Admin/Sys/Files.razor +++ b/Moonlight.Client/UI/Views/Admin/Sys/Files.razor @@ -1,7 +1,6 @@ @page "/admin/system/files" @using Microsoft.AspNetCore.Authorization -@using MoonCore.Blazor.Services @using MoonCore.Helpers @using Moonlight.Client.Implementations @using MoonCore.Blazor.FlyonUi.Files.Manager diff --git a/Moonlight.Client/UI/Views/Admin/Users/Create.razor b/Moonlight.Client/UI/Views/Admin/Users/Create.razor index bf741df6..a08c8183 100644 --- a/Moonlight.Client/UI/Views/Admin/Users/Create.razor +++ b/Moonlight.Client/UI/Views/Admin/Users/Create.razor @@ -1,6 +1,4 @@ @page "/admin/users/create" - -@using System.Text.Json @using MoonCore.Helpers @using Moonlight.Shared.Http.Requests.Admin.Users @using MoonCore.Blazor.FlyonUi.Forms diff --git a/Moonlight.Client/UI/Views/Admin/Users/Update.razor b/Moonlight.Client/UI/Views/Admin/Users/Update.razor index eab94872..1cdcfd16 100644 --- a/Moonlight.Client/UI/Views/Admin/Users/Update.razor +++ b/Moonlight.Client/UI/Views/Admin/Users/Update.razor @@ -1,6 +1,4 @@ @page "/admin/users/{Id:int}" - -@using System.Text.Json @using MoonCore.Helpers @using Moonlight.Shared.Http.Requests.Admin.Users @using Moonlight.Shared.Http.Responses.Admin.Users diff --git a/Moonlight.Client.Runtime/wwwroot/svg/logo.svg b/Moonlight.Client/wwwroot/svg/logo.svg similarity index 100% rename from Moonlight.Client.Runtime/wwwroot/svg/logo.svg rename to Moonlight.Client/wwwroot/svg/logo.svg diff --git a/Moonlight.sln b/Moonlight.sln index 6ee5c7aa..161e93c8 100644 --- a/Moonlight.sln +++ b/Moonlight.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.ApiServer.Runtime EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.Client.Runtime", "Moonlight.Client.Runtime\Moonlight.Client.Runtime.csproj", "{72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Runtime", "Runtime", "{DCE3A43F-ACA8-41C6-BE27-3B3AA033B843}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,5 +40,7 @@ Global {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution + {97FC686D-BC8A-4145-90C7-CA86B598441E} = {DCE3A43F-ACA8-41C6-BE27-3B3AA033B843} + {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05} = {DCE3A43F-ACA8-41C6-BE27-3B3AA033B843} EndGlobalSection EndGlobal