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
|
public class StoreService
|
||||||
{
|
{
|
||||||
|
@ -6,9 +8,16 @@ public class StoreService
|
||||||
|
|
||||||
public StoreAdminService Admin => ServiceProvider.GetRequiredService<StoreAdminService>();
|
public StoreAdminService Admin => ServiceProvider.GetRequiredService<StoreAdminService>();
|
||||||
public StoreOrderService Order => ServiceProvider.GetRequiredService<StoreOrderService>();
|
public StoreOrderService Order => ServiceProvider.GetRequiredService<StoreOrderService>();
|
||||||
|
public readonly List<PaymentGateway> Gateways = new();
|
||||||
|
|
||||||
public StoreService(IServiceProvider serviceProvider)
|
public StoreService(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
ServiceProvider = 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\Middleware\" />
|
||||||
<Folder Include="App\Http\Requests\" />
|
<Folder Include="App\Http\Requests\" />
|
||||||
<Folder Include="App\Http\Resources\" />
|
<Folder Include="App\Http\Resources\" />
|
||||||
|
<Folder Include="storage\logs\" />
|
||||||
|
<Folder Include="storage\mail\" />
|
||||||
<Folder Include="wwwroot\img\" />
|
<Folder Include="wwwroot\img\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -44,4 +46,8 @@
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.0-dev-00923" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<_ContentIncludedByDefault Remove="storage\config.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -29,6 +29,13 @@ Log.Logger = logConfig.CreateLogger();
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
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>();
|
builder.Services.AddDbContext<DataContext>();
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
|
@ -44,7 +51,7 @@ builder.Services.AddScoped<ModalService>();
|
||||||
builder.Services.AddScoped<AlertService>();
|
builder.Services.AddScoped<AlertService>();
|
||||||
|
|
||||||
// Services / Store
|
// Services / Store
|
||||||
builder.Services.AddScoped<StoreService>();
|
builder.Services.AddSingleton<StoreService>();
|
||||||
builder.Services.AddScoped<StoreAdminService>();
|
builder.Services.AddScoped<StoreAdminService>();
|
||||||
builder.Services.AddScoped<StoreOrderService>();
|
builder.Services.AddScoped<StoreOrderService>();
|
||||||
builder.Services.AddScoped<TransactionService>();
|
builder.Services.AddScoped<TransactionService>();
|
||||||
|
@ -97,4 +104,6 @@ app.Services.GetRequiredService<AutoMailSendService>();
|
||||||
var serviceService = app.Services.GetRequiredService<ServiceAdminService>();
|
var serviceService = app.Services.GetRequiredService<ServiceAdminService>();
|
||||||
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());
|
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());
|
||||||
|
|
||||||
|
await pluginService.RunPrePost(app.Services);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
|
@ -3,11 +3,36 @@
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.App.Database.Entities.Store
|
@using Moonlight.App.Database.Entities.Store
|
||||||
@using BlazorTable
|
@using BlazorTable
|
||||||
|
@using Moonlight.App.Services.Store
|
||||||
|
|
||||||
@inject IdentityService IdentityService
|
@inject IdentityService IdentityService
|
||||||
@inject ConfigService ConfigService
|
@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 mt-5">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
Loading…
Reference in a new issue