diff --git a/Moonlight/App/Models/Abstractions/PaymentGateway.cs b/Moonlight/App/Models/Abstractions/PaymentGateway.cs new file mode 100644 index 00000000..06907615 --- /dev/null +++ b/Moonlight/App/Models/Abstractions/PaymentGateway.cs @@ -0,0 +1,8 @@ +namespace Moonlight.App.Models.Abstractions; + +public abstract class PaymentGateway +{ + public abstract string Name { get; } + public abstract string Icon { get; } + public abstract Task Start(double price); +} \ No newline at end of file diff --git a/Moonlight/App/Plugins/Contexts/PluginContext.cs b/Moonlight/App/Plugins/Contexts/PluginContext.cs new file mode 100644 index 00000000..99572ab2 --- /dev/null +++ b/Moonlight/App/Plugins/Contexts/PluginContext.cs @@ -0,0 +1,10 @@ +namespace Moonlight.App.Plugins.Contexts; + +public class PluginContext +{ + public IServiceCollection Services { get; set; } + public IServiceProvider Provider { get; set; } + public IServiceScope Scope { get; set; } + public List PreInitTasks = new(); + public List PostInitTasks = new(); +} \ No newline at end of file diff --git a/Moonlight/App/Plugins/MoonlightPlugin.cs b/Moonlight/App/Plugins/MoonlightPlugin.cs new file mode 100644 index 00000000..b7d413ed --- /dev/null +++ b/Moonlight/App/Plugins/MoonlightPlugin.cs @@ -0,0 +1,10 @@ +using Moonlight.App.Plugins.Contexts; + +namespace Moonlight.App.Plugins; + +public abstract class MoonlightPlugin +{ + public PluginContext Context { get; set; } + public abstract Task Enable(); + public abstract Task Disable(); +} \ No newline at end of file diff --git a/Moonlight/App/Services/PluginService.cs b/Moonlight/App/Services/PluginService.cs new file mode 100644 index 00000000..bc32d9e9 --- /dev/null +++ b/Moonlight/App/Services/PluginService.cs @@ -0,0 +1,120 @@ +using System.Reflection; +using Moonlight.App.Helpers; +using Moonlight.App.Plugins; +using Moonlight.App.Plugins.Contexts; + +namespace Moonlight.App.Services; + +public class PluginService +{ + private readonly List Plugins = new(); + + public async Task Load(IServiceCollection services) + { + var path = PathBuilder.Dir("storage", "plugins"); + Directory.CreateDirectory(path); + + var files = FindFiles(path) + .Where(x => x.EndsWith(".dll")) + .ToArray(); + + foreach (var file in files) + { + try + { + var assembly = Assembly.LoadFile(PathBuilder.File(Directory.GetCurrentDirectory(), file)); + + int plugins = 0; + foreach (var type in assembly.GetTypes()) + { + if (type.IsSubclassOf(typeof(MoonlightPlugin))) + { + try + { + var plugin = (Activator.CreateInstance(type) as MoonlightPlugin)!; + + // Create environment + plugin.Context = new PluginContext() + { + Services = services + }; + + try + { + await plugin.Enable(); + + // After here we can treat the plugin as successfully loaded + plugins++; + Plugins.Add(plugin); + } + catch (Exception e) + { + Logger.Fatal($"Unhandled exception while enabling plugin '{type.Name}'"); + Logger.Fatal(e); + } + } + catch (Exception e) + { + Logger.Fatal($"Failed to create plugin environment for '{type.Name}'"); + Logger.Fatal(e); + } + } + } + + if(plugins == 0) // If 0, we can assume that it was a library dll + Logger.Info($"Loaded {file} as a library"); + else + Logger.Info($"Loaded {plugins} plugin(s) from {file}"); + } + catch (Exception e) + { + Logger.Fatal($"Unable to load assembly from file '{file}'"); + Logger.Fatal(e); + } + } + + Logger.Info($"Loaded {Plugins.Count} plugin(s)"); + } + + public async Task RunPreInit() + { + foreach (var plugin in Plugins) + { + Logger.Info($"Running pre init tasks for {plugin.GetType().Name}"); + + foreach (var preInitTask in plugin.Context.PreInitTasks) + await Task.Run(preInitTask); + } + } + + public async Task RunPrePost(IServiceProvider provider) + { + foreach (var plugin in Plugins) + { + // Pass through the dependency injection + var scope = provider.CreateScope(); + plugin.Context.Provider = scope.ServiceProvider; + plugin.Context.Scope = scope; + + Logger.Info($"Running post init tasks for {plugin.GetType().Name}"); + + foreach (var postInitTask in plugin.Context.PostInitTasks) + await Task.Run(postInitTask); + } + } + + private string[] FindFiles(string dir) + { + var result = new List(); + + foreach (var file in Directory.GetFiles(dir)) + result.Add(file); + + foreach (var directory in Directory.GetDirectories(dir)) + { + result.AddRange(FindFiles(directory)); + } + + return result.ToArray(); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Store/StoreService.cs b/Moonlight/App/Services/Store/StoreService.cs index 1cbf94cf..4f5c4fa8 100644 --- a/Moonlight/App/Services/Store/StoreService.cs +++ b/Moonlight/App/Services/Store/StoreService.cs @@ -1,4 +1,6 @@ -namespace Moonlight.App.Services.Store; +using Moonlight.App.Models.Abstractions; + +namespace Moonlight.App.Services.Store; public class StoreService { @@ -6,9 +8,16 @@ public class StoreService public StoreAdminService Admin => ServiceProvider.GetRequiredService(); public StoreOrderService Order => ServiceProvider.GetRequiredService(); + public readonly List Gateways = new(); public StoreService(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; } + + public Task RegisterGateway(PaymentGateway gateway) + { + Gateways.Add(gateway); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index d72aabb1..6c263fb0 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -18,6 +18,8 @@ + + @@ -44,4 +46,8 @@ + + <_ContentIncludedByDefault Remove="storage\config.json" /> + + diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 3d0d63ea..1c39bbe4 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -29,6 +29,13 @@ Log.Logger = logConfig.CreateLogger(); var builder = WebApplication.CreateBuilder(args); +// Init plugin system +var pluginService = new PluginService(); +builder.Services.AddSingleton(pluginService); + +await pluginService.Load(builder.Services); +await pluginService.RunPreInit(); + builder.Services.AddDbContext(); // Repositories @@ -44,7 +51,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); // Services / Store -builder.Services.AddScoped(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -97,4 +104,6 @@ app.Services.GetRequiredService(); var serviceService = app.Services.GetRequiredService(); await serviceService.RegisterAction(ServiceType.Server, new DummyActions()); +await pluginService.RunPrePost(app.Services); + app.Run(); \ No newline at end of file diff --git a/Moonlight/Shared/Views/Account/Payments.razor b/Moonlight/Shared/Views/Account/Payments.razor index e154bf2e..59b6d072 100644 --- a/Moonlight/Shared/Views/Account/Payments.razor +++ b/Moonlight/Shared/Views/Account/Payments.razor @@ -3,11 +3,36 @@ @using Moonlight.App.Services @using Moonlight.App.Database.Entities.Store @using BlazorTable +@using Moonlight.App.Services.Store @inject IdentityService IdentityService @inject ConfigService ConfigService +@inject StoreService StoreService - + + +
+
+
+ +
+
+
+
+
+ @foreach (var gateway in StoreService.Gateways) + { + + } +
+
+
+