Made implementation api cleaner

This commit is contained in:
Baumgartner Marcel 2023-11-15 10:25:28 +01:00
parent d55490dd51
commit e8706cad1c
20 changed files with 139 additions and 131 deletions

View file

@ -1,5 +1,6 @@
using Moonlight.App.Database.Entities.Store; using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Models.Abstractions; using Moonlight.App.Models.Abstractions;
using Moonlight.App.Models.Abstractions.Services;
namespace Moonlight.App.Actions.Dummy; namespace Moonlight.App.Actions.Dummy;

View file

@ -1,6 +1,4 @@
using System.ComponentModel; using System.ComponentModel;
using Moonlight.App.Database.Entities;
using Moonlight.App.Extensions.Attributes;
namespace Moonlight.App.Actions.Dummy; namespace Moonlight.App.Actions.Dummy;

View file

@ -0,0 +1,25 @@
using Moonlight.App.Actions.Dummy.Layouts;
using Moonlight.App.Actions.Dummy.Pages;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Abstractions.Services;
namespace Moonlight.App.Actions.Dummy;
public class DummyServiceDefinition : ServiceDefinition
{
public override ServiceActions Actions => new DummyActions();
public override Type ConfigType => typeof(DummyConfig);
public override async Task BuildUserView(ServiceViewContext context)
{
context.Layout = ComponentHelper.FromType<DummyUser>();
await context.AddPage<DummyPage>("Demo", "/demo");
}
public override Task BuildAdminView(ServiceViewContext context)
{
context.Layout = ComponentHelper.FromType<DummyAdmin>();
return Task.CompletedTask;
}
}

View file

@ -1,34 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Actions.Dummy.Layouts;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Abstractions;
namespace Moonlight.App.Actions.Dummy;
public class DummyServiceImplementation : ServiceImplementation
{
public override ServiceActions Actions { get; } = new DummyActions();
public override Type ConfigType { get; } = typeof(DummyConfig);
public override RenderFragment GetAdminLayout()
{
return ComponentHelper.FromType(typeof(DummyAdmin));
}
public override RenderFragment GetUserLayout()
{
return ComponentHelper.FromType(typeof(DummyUser));
}
public override ServiceUiPage[] GetUserPages(Service service, User user)
{
return Array.Empty<ServiceUiPage>();
}
public override ServiceUiPage[] GetAdminPages(Service service, User user)
{
return Array.Empty<ServiceUiPage>();
}
}

View file

@ -17,4 +17,7 @@ public static class ComponentHelper
builder.CloseComponent(); builder.CloseComponent();
}; };
public static RenderFragment FromType<T>(Action<Dictionary<string, object>>? buildAttributes = null) where T : ComponentBase =>
FromType(typeof(T), buildAttributes);
} }

View file

@ -1,10 +0,0 @@
using Moonlight.App.Database.Entities.Store;
namespace Moonlight.App.Models.Abstractions;
public abstract class ServiceActions
{
public abstract Task Create(IServiceProvider provider, Service service);
public abstract Task Update(IServiceProvider provider, Service service);
public abstract Task Delete(IServiceProvider provider, Service service);
}

View file

@ -1,18 +0,0 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Store;
namespace Moonlight.App.Models.Abstractions;
public abstract class ServiceImplementation
{
public abstract ServiceActions Actions { get; }
public abstract Type ConfigType { get; }
public abstract RenderFragment GetAdminLayout();
public abstract RenderFragment GetUserLayout();
// The service and user parameter can be used to only show certain pages to admins or other
public abstract ServiceUiPage[] GetUserPages(Service service, User user);
public abstract ServiceUiPage[] GetAdminPages(Service service, User user);
}

View file

@ -0,0 +1,8 @@
namespace Moonlight.App.Models.Abstractions.Services;
public abstract class ServiceActions
{
public abstract Task Create(IServiceProvider provider, Database.Entities.Store.Service service);
public abstract Task Update(IServiceProvider provider, Database.Entities.Store.Service service);
public abstract Task Delete(IServiceProvider provider, Database.Entities.Store.Service service);
}

View file

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Models.Abstractions.Services;
public abstract class ServiceDefinition
{
// Config
public abstract ServiceActions Actions { get; }
public abstract Type ConfigType { get; }
// Methods
public abstract Task BuildUserView(ServiceViewContext context);
public abstract Task BuildAdminView(ServiceViewContext context);
}

View file

@ -1,11 +1,11 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Moonlight.App.Models.Abstractions; namespace Moonlight.App.Models.Abstractions.Services;
public class ServiceUiPage public class ServiceUiPage
{ {
public string Name { get; set; } public string Name { get; set; }
public string Route { get; set; } public string Route { get; set; }
public string Icon { get; set; } public string Icon { get; set; }
public ComponentBase Component { get; set; } public RenderFragment Component { get; set; }
} }

View file

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Components;
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Helpers;
namespace Moonlight.App.Models.Abstractions.Services;
public class ServiceViewContext
{
// Meta
public Service Service { get; set; }
public User User { get; set; }
public Product Product { get; set; }
// Content
public List<ServiceUiPage> Pages { get; set; } = new();
public RenderFragment Layout { get; set; }
public Task AddPage<T>(string name, string route, string icon = "") where T : ComponentBase
{
Pages.Add(new()
{
Name = name,
Route = route,
Icon = icon,
Component = ComponentHelper.FromType<T>()
});
return Task.CompletedTask;
}
}

View file

@ -1,6 +1,4 @@
using Moonlight.App.Database.Entities; using Moonlight.App.Models.Abstractions.Services;
using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Models.Abstractions;
namespace Moonlight.App.Plugins.Contexts; namespace Moonlight.App.Plugins.Contexts;
@ -13,5 +11,6 @@ public class PluginContext
public WebApplication WebApplication { get; set; } public WebApplication WebApplication { get; set; }
public List<Action> PreInitTasks = new(); public List<Action> PreInitTasks = new();
public List<Action> PostInitTasks = new(); public List<Action> PostInitTasks = new();
public Action<List<ServiceUiPage>, ServiceManageContext>? BuildServiceUiPages = null; public Action<ServiceViewContext>? BuildUserServiceView { get; set; } = null;
public Action<ServiceViewContext>? BuildAdminServiceView { get; set; } = null;
} }

View file

@ -1,11 +0,0 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Store;
namespace Moonlight.App.Plugins.Contexts;
public class ServiceManageContext
{
public Service Service { get; set; }
public User User { get; set; }
public Product Product { get; set; }
}

View file

@ -3,6 +3,7 @@ using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.Store; using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Models.Abstractions; using Moonlight.App.Models.Abstractions;
using Moonlight.App.Models.Abstractions.Services;
using Moonlight.App.Plugins; using Moonlight.App.Plugins;
using Moonlight.App.Plugins.Contexts; using Moonlight.App.Plugins.Contexts;
@ -108,18 +109,24 @@ public class PluginService
} }
} }
public Task<ServiceUiPage[]> BuildServiceUiPages(ServiceUiPage[] pages, ServiceManageContext context) public Task BuildUserServiceView(ServiceViewContext context)
{ {
var list = pages.ToList();
foreach (var plugin in Plugins) foreach (var plugin in Plugins)
{ {
// Only build if the plugin adds a page plugin.Context.BuildUserServiceView?.Invoke(context);
if(plugin.Context.BuildServiceUiPages != null)
plugin.Context.BuildServiceUiPages.Invoke(list, context);
} }
return Task.FromResult(list.ToArray()); return Task.CompletedTask;
}
public Task BuildAdminServiceView(ServiceViewContext context)
{
foreach (var plugin in Plugins)
{
plugin.Context.BuildAdminServiceView?.Invoke(context);
}
return Task.CompletedTask;
} }
private string[] FindFiles(string dir) private string[] FindFiles(string dir)

View file

@ -9,17 +9,17 @@ namespace Moonlight.App.Services.ServiceManage;
public class ServiceAdminService public class ServiceAdminService
{ {
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly ServiceTypeService ServiceTypeService; private readonly ServiceDefinitionService ServiceDefinitionService;
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceTypeService serviceTypeService) public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceDefinitionService serviceDefinitionService)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
ServiceTypeService = serviceTypeService; ServiceDefinitionService = serviceDefinitionService;
} }
public async Task<Service> Create(User u, Product p, Action<Service>? modifyService = null) public async Task<Service> Create(User u, Product p, Action<Service>? modifyService = null)
{ {
var impl = ServiceTypeService.Get(p); var impl = ServiceDefinitionService.Get(p);
// Load models in new scope // Load models in new scope
using var scope = ServiceScopeFactory.CreateScope(); using var scope = ServiceScopeFactory.CreateScope();
@ -66,7 +66,7 @@ public class ServiceAdminService
if (service == null) if (service == null)
throw new DisplayException("Service does not exist anymore"); throw new DisplayException("Service does not exist anymore");
var impl = ServiceTypeService.Get(service); var impl = ServiceDefinitionService.Get(service);
await impl.Actions.Delete(scope.ServiceProvider, service); await impl.Actions.Delete(scope.ServiceProvider, service);

View file

@ -2,24 +2,25 @@
using Moonlight.App.Database.Entities.Store; using Moonlight.App.Database.Entities.Store;
using Moonlight.App.Database.Enums; using Moonlight.App.Database.Enums;
using Moonlight.App.Models.Abstractions; using Moonlight.App.Models.Abstractions;
using Moonlight.App.Models.Abstractions.Services;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
namespace Moonlight.App.Services.ServiceManage; namespace Moonlight.App.Services.ServiceManage;
public class ServiceTypeService public class ServiceDefinitionService
{ {
private readonly Dictionary<ServiceType, ServiceImplementation> ServiceImplementations = new(); private readonly Dictionary<ServiceType, ServiceDefinition> ServiceImplementations = new();
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
public ServiceTypeService(IServiceScopeFactory serviceScopeFactory) public ServiceDefinitionService(IServiceScopeFactory serviceScopeFactory)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
} }
public void Register<T>(ServiceType type) where T : ServiceImplementation public void Register<T>(ServiceType type) where T : ServiceDefinition
{ {
var impl = Activator.CreateInstance<T>() as ServiceImplementation; var impl = Activator.CreateInstance<T>() as ServiceDefinition;
if (impl == null) if (impl == null)
throw new ArgumentException("The provided type is not an service implementation"); throw new ArgumentException("The provided type is not an service implementation");
@ -30,7 +31,7 @@ public class ServiceTypeService
ServiceImplementations.Add(type, impl); ServiceImplementations.Add(type, impl);
} }
public ServiceImplementation Get(Service s) public ServiceDefinition Get(Service s)
{ {
using var scope = ServiceScopeFactory.CreateScope(); using var scope = ServiceScopeFactory.CreateScope();
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>(); var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
@ -43,9 +44,9 @@ public class ServiceTypeService
return Get(service.Product); return Get(service.Product);
} }
public ServiceImplementation Get(Product p) => Get(p.Type); public ServiceDefinition Get(Product p) => Get(p.Type);
public ServiceImplementation Get(ServiceType type) public ServiceDefinition Get(ServiceType type)
{ {
if (!ServiceImplementations.ContainsKey(type)) if (!ServiceImplementations.ContainsKey(type))
throw new ArgumentException($"No service implementation found for {type}"); throw new ArgumentException($"No service implementation found for {type}");

View file

@ -13,7 +13,7 @@ public class ServiceService // This service is used for managing services and cr
private readonly Repository<User> UserRepository; private readonly Repository<User> UserRepository;
public ServiceAdminService Admin => ServiceProvider.GetRequiredService<ServiceAdminService>(); public ServiceAdminService Admin => ServiceProvider.GetRequiredService<ServiceAdminService>();
public ServiceTypeService Type => ServiceProvider.GetRequiredService<ServiceTypeService>(); public ServiceDefinitionService Definition => ServiceProvider.GetRequiredService<ServiceDefinitionService>();
public ServiceManageService Manage => ServiceProvider.GetRequiredService<ServiceManageService>(); public ServiceManageService Manage => ServiceProvider.GetRequiredService<ServiceManageService>();
public ServiceService(IServiceProvider serviceProvider, Repository<Service> serviceRepository, Repository<User> userRepository) public ServiceService(IServiceProvider serviceProvider, Repository<Service> serviceRepository, Repository<User> userRepository)

View file

@ -108,7 +108,7 @@ public class StoreAdminService
{ {
try try
{ {
var impl = ServiceService.Type.Get(type); var impl = ServiceService.Definition.Get(type);
return impl.ConfigType; return impl.ConfigType;
} }
catch (ArgumentException) catch (ArgumentException)
@ -123,7 +123,7 @@ public class StoreAdminService
} }
public object GetProductConfig(Product product) public object GetProductConfig(Product product)
{ {
var impl = ServiceService.Type.Get(product.Type); var impl = ServiceService.Definition.Get(product.Type);
return JsonConvert.DeserializeObject(product.ConfigJson, impl.ConfigType) ?? return JsonConvert.DeserializeObject(product.ConfigJson, impl.ConfigType) ??
CreateNewProductConfig(product.Type); CreateNewProductConfig(product.Type);

View file

@ -80,7 +80,7 @@ builder.Services.AddSingleton<AutoMailSendService>();
// Services / ServiceManage // Services / ServiceManage
builder.Services.AddScoped<ServiceService>(); builder.Services.AddScoped<ServiceService>();
builder.Services.AddSingleton<ServiceAdminService>(); builder.Services.AddSingleton<ServiceAdminService>();
builder.Services.AddSingleton<ServiceTypeService>(); builder.Services.AddSingleton<ServiceDefinitionService>();
builder.Services.AddSingleton<ServiceManageService>(); builder.Services.AddSingleton<ServiceManageService>();
// Services / Ticketing // Services / Ticketing
@ -123,9 +123,9 @@ app.MapControllers();
// Auto start background services // Auto start background services
app.Services.GetRequiredService<AutoMailSendService>(); app.Services.GetRequiredService<AutoMailSendService>();
var serviceService = app.Services.GetRequiredService<ServiceTypeService>(); var serviceService = app.Services.GetRequiredService<ServiceDefinitionService>();
serviceService.Register<DummyServiceImplementation>(ServiceType.Server); serviceService.Register<DummyServiceDefinition>(ServiceType.Server);
await pluginService.RunPrePost(app); await pluginService.RunPrePost(app);

View file

@ -5,6 +5,7 @@
@using Moonlight.App.Services.ServiceManage @using Moonlight.App.Services.ServiceManage
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Moonlight.App.Models.Abstractions @using Moonlight.App.Models.Abstractions
@using Moonlight.App.Models.Abstractions.Services
@using Moonlight.App.Services @using Moonlight.App.Services
@inject Repository<Service> ServiceRepository @inject Repository<Service> ServiceRepository
@ -20,10 +21,10 @@
else else
{ {
<CascadingValue Name="Service" Value="Service"> <CascadingValue Name="Service" Value="Service">
<CascadingValue Name="Implementation" Value="Implementation"> <CascadingValue Name="Implementation" Value="Definition">
<CascadingValue Name="Route" Value="Route"> <CascadingValue Name="Route" Value="Route">
<CascadingValue Name="Pages" Value="ServiceUiPages"> <CascadingValue Name="ViewContext" Value="ViewContext">
@Implementation.GetUserLayout() @ViewContext.Layout
</CascadingValue> </CascadingValue>
</CascadingValue> </CascadingValue>
</CascadingValue> </CascadingValue>
@ -40,8 +41,8 @@
public string? Route { get; set; } public string? Route { get; set; }
private Service? Service; private Service? Service;
private ServiceImplementation Implementation; private ServiceDefinition Definition;
private ServiceUiPage[] ServiceUiPages; private ServiceViewContext ViewContext;
private async Task Load(LazyLoader lazyLoader) private async Task Load(LazyLoader lazyLoader)
{ {
@ -64,29 +65,21 @@
if (Service == null) if (Service == null)
return; return;
// Load implementation
await lazyLoader.SetText("Loading implementation"); await lazyLoader.SetText("Loading implementation");
Definition = ServiceService.Definition.Get(Service.Product.Type);
Implementation = ServiceService.Type.Get(Service.Product.Type); // Build dynamic user interface
await lazyLoader.SetText("Building dynamic user interface");
await lazyLoader.SetText("Building ui"); ViewContext = new ServiceViewContext()
// Build ui pages
List<ServiceUiPage> pagesWithoutPlugins = new();
// -- Add default here --
// Add implementation pages
pagesWithoutPlugins.AddRange(Implementation.GetUserPages(Service, IdentityService.CurrentUser));
// Modify pages through plugins
ServiceUiPages = await PluginService.BuildServiceUiPages(pagesWithoutPlugins.ToArray(), new()
{ {
Product = Service.Product,
Service = Service, Service = Service,
Product = Service.Product,
User = IdentityService.CurrentUser User = IdentityService.CurrentUser
}); };
// Done :D await Definition.BuildUserView(ViewContext);
await PluginService.BuildUserServiceView(ViewContext);
} }
} }