Started implementing modular payment gateways. Implemented a basic plugin system
TODO: Add capability for plugins to modify the kestrel pipeline
This commit is contained in:
parent
ff9bcc6433
commit
863a002370
8
Moonlight/App/Models/Abstractions/PaymentGateway.cs
Normal file
8
Moonlight/App/Models/Abstractions/PaymentGateway.cs
Normal file
|
@ -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<string> Start(double price);
|
||||
}
|
10
Moonlight/App/Plugins/Contexts/PluginContext.cs
Normal file
10
Moonlight/App/Plugins/Contexts/PluginContext.cs
Normal file
|
@ -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<Action> PreInitTasks = new();
|
||||
public List<Action> PostInitTasks = new();
|
||||
}
|
10
Moonlight/App/Plugins/MoonlightPlugin.cs
Normal file
10
Moonlight/App/Plugins/MoonlightPlugin.cs
Normal file
|
@ -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();
|
||||
}
|
120
Moonlight/App/Services/PluginService.cs
Normal file
120
Moonlight/App/Services/PluginService.cs
Normal file
|
@ -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<MoonlightPlugin> 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<string>();
|
||||
|
||||
foreach (var file in Directory.GetFiles(dir))
|
||||
result.Add(file);
|
||||
|
||||
foreach (var directory in Directory.GetDirectories(dir))
|
||||
{
|
||||
result.AddRange(FindFiles(directory));
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
|
@ -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<StoreAdminService>();
|
||||
public StoreOrderService Order => ServiceProvider.GetRequiredService<StoreOrderService>();
|
||||
public readonly List<PaymentGateway> Gateways = new();
|
||||
|
||||
public StoreService(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public Task RegisterGateway(PaymentGateway gateway)
|
||||
{
|
||||
Gateways.Add(gateway);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@
|
|||
<Folder Include="App\Http\Middleware\" />
|
||||
<Folder Include="App\Http\Requests\" />
|
||||
<Folder Include="App\Http\Resources\" />
|
||||
<Folder Include="storage\logs\" />
|
||||
<Folder Include="storage\mail\" />
|
||||
<Folder Include="wwwroot\img\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -44,4 +46,8 @@
|
|||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="storage\config.json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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<DataContext>();
|
||||
|
||||
// Repositories
|
||||
|
@ -44,7 +51,7 @@ builder.Services.AddScoped<ModalService>();
|
|||
builder.Services.AddScoped<AlertService>();
|
||||
|
||||
// Services / Store
|
||||
builder.Services.AddScoped<StoreService>();
|
||||
builder.Services.AddSingleton<StoreService>();
|
||||
builder.Services.AddScoped<StoreAdminService>();
|
||||
builder.Services.AddScoped<StoreOrderService>();
|
||||
builder.Services.AddScoped<TransactionService>();
|
||||
|
@ -97,4 +104,6 @@ app.Services.GetRequiredService<AutoMailSendService>();
|
|||
var serviceService = app.Services.GetRequiredService<ServiceAdminService>();
|
||||
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());
|
||||
|
||||
await pluginService.RunPrePost(app.Services);
|
||||
|
||||
app.Run();
|
|
@ -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
|
||||
|
||||
<AccountNavigation Index="2" />
|
||||
<AccountNavigation Index="2"/>
|
||||
|
||||
<div class="row mt-5">
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="card card-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="card card-body">
|
||||
<div class="row">
|
||||
@foreach (var gateway in StoreService.Gateways)
|
||||
{
|
||||
<div class="col-md-4 col-12">
|
||||
<a @onclick:preventDefault href="#" class="card card-body bg-hover-info text-center border border-info">
|
||||
<i class="@(gateway.Icon)"></i>
|
||||
<h4 class="fw-bold mb-0 align-middle">@(gateway.Name)</h4>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-header">
|
||||
|
|
Loading…
Reference in a new issue