Started working on service implementation api
This commit is contained in:
13
Moonlight/App/Actions/Dummy/DummyConfig.cs
Normal file
13
Moonlight/App/Actions/Dummy/DummyConfig.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Extensions.Attributes;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Actions.Dummy;
|
||||||
|
|
||||||
|
public class DummyConfig
|
||||||
|
{
|
||||||
|
[Description("Some description")]
|
||||||
|
public string String { get; set; } = "";
|
||||||
|
public bool Boolean { get; set; }
|
||||||
|
public int Integer { get; set; }
|
||||||
|
}
|
||||||
34
Moonlight/App/Actions/Dummy/DummyServiceImplementation.cs
Normal file
34
Moonlight/App/Actions/Dummy/DummyServiceImplementation.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
Moonlight/App/Actions/Dummy/Layouts/DummyAdmin.razor
Normal file
5
Moonlight/App/Actions/Dummy/Layouts/DummyAdmin.razor
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<h3>DummyAdmin</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
5
Moonlight/App/Actions/Dummy/Layouts/DummyUser.razor
Normal file
5
Moonlight/App/Actions/Dummy/Layouts/DummyUser.razor
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<h3>DummyUser</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
5
Moonlight/App/Actions/Dummy/Pages/DummyPage.razor
Normal file
5
Moonlight/App/Actions/Dummy/Pages/DummyPage.razor
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<h3>DummyPage</h3>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,9 +4,17 @@ namespace Moonlight.App.Helpers;
|
|||||||
|
|
||||||
public static class ComponentHelper
|
public static class ComponentHelper
|
||||||
{
|
{
|
||||||
public static RenderFragment FromType(Type type) => builder =>
|
public static RenderFragment FromType(Type type, Action<Dictionary<string, object>>? buildAttributes = null) => builder =>
|
||||||
{
|
{
|
||||||
builder.OpenComponent(0, type);
|
builder.OpenComponent(0, type);
|
||||||
|
|
||||||
|
if (buildAttributes != null)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> parameters = new();
|
||||||
|
buildAttributes.Invoke(parameters);
|
||||||
|
builder.AddMultipleAttributes(1, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
builder.CloseComponent();
|
builder.CloseComponent();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
18
Moonlight/App/Models/Abstractions/ServiceImplementation.cs
Normal file
18
Moonlight/App/Models/Abstractions/ServiceImplementation.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
11
Moonlight/App/Models/Abstractions/ServiceUiPage.cs
Normal file
11
Moonlight/App/Models/Abstractions/ServiceUiPage.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Abstractions;
|
||||||
|
|
||||||
|
public class ServiceUiPage
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Route { get; set; }
|
||||||
|
public string Icon { get; set; }
|
||||||
|
public ComponentBase Component { get; set; }
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ public enum Permission
|
|||||||
AdminUsersEdit = 1003,
|
AdminUsersEdit = 1003,
|
||||||
AdminTickets = 1004,
|
AdminTickets = 1004,
|
||||||
AdminCommunity = 1030,
|
AdminCommunity = 1030,
|
||||||
|
AdminServices = 1050,
|
||||||
AdminStore = 1900,
|
AdminStore = 1900,
|
||||||
AdminViewExceptions = 1999,
|
AdminViewExceptions = 1999,
|
||||||
AdminRoot = 2000
|
AdminRoot = 2000
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
namespace Moonlight.App.Plugins.Contexts;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Database.Entities.Store;
|
||||||
|
using Moonlight.App.Models.Abstractions;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Plugins.Contexts;
|
||||||
|
|
||||||
public class PluginContext
|
public class PluginContext
|
||||||
{
|
{
|
||||||
@@ -9,4 +13,5 @@ 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;
|
||||||
}
|
}
|
||||||
11
Moonlight/App/Plugins/Contexts/ServiceManageContext.cs
Normal file
11
Moonlight/App/Plugins/Contexts/ServiceManageContext.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Database.Entities.Store;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Models.Abstractions;
|
||||||
using Moonlight.App.Plugins;
|
using Moonlight.App.Plugins;
|
||||||
using Moonlight.App.Plugins.Contexts;
|
using Moonlight.App.Plugins.Contexts;
|
||||||
|
|
||||||
@@ -105,6 +108,20 @@ public class PluginService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<ServiceUiPage[]> BuildServiceUiPages(ServiceUiPage[] pages, ServiceManageContext context)
|
||||||
|
{
|
||||||
|
var list = pages.ToList();
|
||||||
|
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
{
|
||||||
|
// Only build if the plugin adds a page
|
||||||
|
if(plugin.Context.BuildServiceUiPages != null)
|
||||||
|
plugin.Context.BuildServiceUiPages.Invoke(list, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(list.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
private string[] FindFiles(string dir)
|
private string[] FindFiles(string dir)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Database.Entities.Store;
|
using Moonlight.App.Database.Entities.Store;
|
||||||
using Moonlight.App.Database.Enums;
|
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Models.Abstractions;
|
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.ServiceManage;
|
namespace Moonlight.App.Services.ServiceManage;
|
||||||
|
|
||||||
public class ServiceAdminService
|
public class ServiceAdminService
|
||||||
{
|
{
|
||||||
public readonly Dictionary<ServiceType, ServiceActions> Actions = new();
|
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
private readonly ServiceTypeService ServiceTypeService;
|
||||||
|
|
||||||
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory)
|
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceTypeService serviceTypeService)
|
||||||
{
|
{
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
ServiceTypeService = serviceTypeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (!Actions.ContainsKey(p.Type))
|
var impl = ServiceTypeService.Get(p);
|
||||||
throw new DisplayException($"The product type {p.Type} is not registered");
|
|
||||||
|
|
||||||
// Load models in new scope
|
// Load models in new scope
|
||||||
using var scope = ServiceScopeFactory.CreateScope();
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
@@ -49,8 +47,7 @@ public class ServiceAdminService
|
|||||||
var finishedService = serviceRepo.Add(service);
|
var finishedService = serviceRepo.Add(service);
|
||||||
|
|
||||||
// Call the action for the logic behind the service type
|
// Call the action for the logic behind the service type
|
||||||
var actions = Actions[product.Type];
|
await impl.Actions.Create(scope.ServiceProvider, finishedService);
|
||||||
await actions.Create(scope.ServiceProvider, finishedService);
|
|
||||||
|
|
||||||
return finishedService;
|
return finishedService;
|
||||||
}
|
}
|
||||||
@@ -63,17 +60,15 @@ public class ServiceAdminService
|
|||||||
|
|
||||||
var service = serviceRepo
|
var service = serviceRepo
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.Product)
|
|
||||||
.Include(x => x.Shares)
|
.Include(x => x.Shares)
|
||||||
.FirstOrDefault(x => x.Id == s.Id);
|
.FirstOrDefault(x => x.Id == s.Id);
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
throw new DisplayException("Service does not exist anymore");
|
throw new DisplayException("Service does not exist anymore");
|
||||||
|
|
||||||
if (!Actions.ContainsKey(service.Product.Type))
|
var impl = ServiceTypeService.Get(service);
|
||||||
throw new DisplayException($"The product type {service.Product.Type} is not registered");
|
|
||||||
|
|
||||||
await Actions[service.Product.Type].Delete(scope.ServiceProvider, service);
|
await impl.Actions.Delete(scope.ServiceProvider, service);
|
||||||
|
|
||||||
foreach (var share in service.Shares.ToArray())
|
foreach (var share in service.Shares.ToArray())
|
||||||
{
|
{
|
||||||
@@ -82,10 +77,4 @@ public class ServiceAdminService
|
|||||||
|
|
||||||
serviceRepo.Delete(service);
|
serviceRepo.Delete(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RegisterAction(ServiceType type, ServiceActions actions) // Use this function to register service types
|
|
||||||
{
|
|
||||||
Actions.Add(type, actions);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
48
Moonlight/App/Services/ServiceManage/ServiceManageService.cs
Normal file
48
Moonlight/App/Services/ServiceManage/ServiceManageService.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Database.Entities.Store;
|
||||||
|
using Moonlight.App.Models.Abstractions;
|
||||||
|
using Moonlight.App.Models.Enums;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.ServiceManage;
|
||||||
|
|
||||||
|
public class ServiceManageService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public ServiceManageService(IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> CheckAccess(Service s, User user)
|
||||||
|
{
|
||||||
|
var permissionStorage = new PermissionStorage(user.Permissions);
|
||||||
|
|
||||||
|
// Is admin?
|
||||||
|
if(permissionStorage[Permission.AdminServices])
|
||||||
|
return Task.FromResult(true);
|
||||||
|
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
|
||||||
|
|
||||||
|
var service = serviceRepo
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Owner)
|
||||||
|
.Include(x => x.Shares)
|
||||||
|
.ThenInclude(x => x.User)
|
||||||
|
.First(x => x.Id == s.Id);
|
||||||
|
|
||||||
|
// Is owner?
|
||||||
|
if(service.Owner.Id == user.Id)
|
||||||
|
return Task.FromResult(true);
|
||||||
|
|
||||||
|
// Is shared user
|
||||||
|
if(service.Shares.Any(x => x.User.Id == user.Id))
|
||||||
|
return Task.FromResult(true);
|
||||||
|
|
||||||
|
// No match
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ 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 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)
|
||||||
{
|
{
|
||||||
|
|||||||
55
Moonlight/App/Services/ServiceManage/ServiceTypeService.cs
Normal file
55
Moonlight/App/Services/ServiceManage/ServiceTypeService.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database.Entities.Store;
|
||||||
|
using Moonlight.App.Database.Enums;
|
||||||
|
using Moonlight.App.Models.Abstractions;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.ServiceManage;
|
||||||
|
|
||||||
|
public class ServiceTypeService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ServiceType, ServiceImplementation> ServiceImplementations = new();
|
||||||
|
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
|
||||||
|
public ServiceTypeService(IServiceScopeFactory serviceScopeFactory)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register<T>(ServiceType type) where T : ServiceImplementation
|
||||||
|
{
|
||||||
|
var impl = Activator.CreateInstance<T>() as ServiceImplementation;
|
||||||
|
|
||||||
|
if (impl == null)
|
||||||
|
throw new ArgumentException("The provided type is not an service implementation");
|
||||||
|
|
||||||
|
if (ServiceImplementations.ContainsKey(type))
|
||||||
|
throw new ArgumentException($"An implementation for {type} has already been registered");
|
||||||
|
|
||||||
|
ServiceImplementations.Add(type, impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceImplementation Get(Service s)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var serviceRepo = scope.ServiceProvider.GetRequiredService<Repository<Service>>();
|
||||||
|
|
||||||
|
var service = serviceRepo
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Product)
|
||||||
|
.First(x => x.Id == s.Id);
|
||||||
|
|
||||||
|
return Get(service.Product);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceImplementation Get(Product p) => Get(p.Type);
|
||||||
|
|
||||||
|
public ServiceImplementation Get(ServiceType type)
|
||||||
|
{
|
||||||
|
if (!ServiceImplementations.ContainsKey(type))
|
||||||
|
throw new ArgumentException($"No service implementation found for {type}");
|
||||||
|
|
||||||
|
return ServiceImplementations[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
using Moonlight.App.Database.Enums;
|
using Moonlight.App.Database.Enums;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.ServiceManage;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Store;
|
namespace Moonlight.App.Services.Store;
|
||||||
|
|
||||||
@@ -9,11 +11,16 @@ public class StoreAdminService
|
|||||||
{
|
{
|
||||||
private readonly Repository<Product> ProductRepository;
|
private readonly Repository<Product> ProductRepository;
|
||||||
private readonly Repository<Category> CategoryRepository;
|
private readonly Repository<Category> CategoryRepository;
|
||||||
|
private readonly ServiceService ServiceService;
|
||||||
|
|
||||||
public StoreAdminService(Repository<Product> productRepository, Repository<Category> categoryRepository)
|
public StoreAdminService(
|
||||||
|
Repository<Product> productRepository,
|
||||||
|
Repository<Category> categoryRepository,
|
||||||
|
ServiceService serviceService)
|
||||||
{
|
{
|
||||||
ProductRepository = productRepository;
|
ProductRepository = productRepository;
|
||||||
CategoryRepository = categoryRepository;
|
CategoryRepository = categoryRepository;
|
||||||
|
ServiceService = serviceService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Category> AddCategory(string name, string description, string slug)
|
public Task<Category> AddCategory(string name, string description, string slug)
|
||||||
@@ -31,8 +38,7 @@ public class StoreAdminService
|
|||||||
return Task.FromResult(result);
|
return Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Product> AddProduct(string name, string description, string slug, ServiceType type, string configJson,
|
public Task<Product> AddProduct(string name, string description, string slug, ServiceType type, Action<Product>? modifyProduct = null)
|
||||||
Action<Product>? modifyProduct = null)
|
|
||||||
{
|
{
|
||||||
if (ProductRepository.Get().Any(x => x.Slug == slug))
|
if (ProductRepository.Get().Any(x => x.Slug == slug))
|
||||||
throw new DisplayException("A product with that slug does already exist");
|
throw new DisplayException("A product with that slug does already exist");
|
||||||
@@ -43,7 +49,7 @@ public class StoreAdminService
|
|||||||
Description = description,
|
Description = description,
|
||||||
Slug = slug,
|
Slug = slug,
|
||||||
Type = type,
|
Type = type,
|
||||||
ConfigJson = configJson
|
ConfigJson = "{}"
|
||||||
};
|
};
|
||||||
|
|
||||||
if(modifyProduct != null)
|
if(modifyProduct != null)
|
||||||
@@ -96,4 +102,36 @@ public class StoreAdminService
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Product config
|
||||||
|
public Type GetProductConfigType(ServiceType type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var impl = ServiceService.Type.Get(type);
|
||||||
|
return impl.ConfigType;
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
return typeof(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public object CreateNewProductConfig(ServiceType type)
|
||||||
|
{
|
||||||
|
var config = Activator.CreateInstance(GetProductConfigType(type))!;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
public object GetProductConfig(Product product)
|
||||||
|
{
|
||||||
|
var impl = ServiceService.Type.Get(product.Type);
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject(product.ConfigJson, impl.ConfigType) ??
|
||||||
|
CreateNewProductConfig(product.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveProductConfig(Product product, object config)
|
||||||
|
{
|
||||||
|
product.ConfigJson = JsonConvert.SerializeObject(config);
|
||||||
|
ProductRepository.Update(product);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ namespace Moonlight.App.Services.Users;
|
|||||||
public class UserDeleteService
|
public class UserDeleteService
|
||||||
{
|
{
|
||||||
private readonly Repository<Service> ServiceRepository;
|
private readonly Repository<Service> ServiceRepository;
|
||||||
|
private readonly Repository<ServiceShare> ServiceShareRepository;
|
||||||
private readonly Repository<Post> PostRepository;
|
private readonly Repository<Post> PostRepository;
|
||||||
private readonly Repository<User> UserRepository;
|
private readonly Repository<User> UserRepository;
|
||||||
private readonly Repository<Transaction> TransactionRepository;
|
private readonly Repository<Transaction> TransactionRepository;
|
||||||
@@ -32,7 +33,8 @@ public class UserDeleteService
|
|||||||
Repository<CouponUse> couponUseRepository,
|
Repository<CouponUse> couponUseRepository,
|
||||||
Repository<Transaction> transactionRepository,
|
Repository<Transaction> transactionRepository,
|
||||||
Repository<Ticket> ticketRepository,
|
Repository<Ticket> ticketRepository,
|
||||||
Repository<TicketMessage> ticketMessageRepository)
|
Repository<TicketMessage> ticketMessageRepository,
|
||||||
|
Repository<ServiceShare> serviceShareRepository)
|
||||||
{
|
{
|
||||||
ServiceRepository = serviceRepository;
|
ServiceRepository = serviceRepository;
|
||||||
ServiceService = serviceService;
|
ServiceService = serviceService;
|
||||||
@@ -44,6 +46,7 @@ public class UserDeleteService
|
|||||||
TransactionRepository = transactionRepository;
|
TransactionRepository = transactionRepository;
|
||||||
TicketRepository = ticketRepository;
|
TicketRepository = ticketRepository;
|
||||||
TicketMessageRepository = ticketMessageRepository;
|
TicketMessageRepository = ticketMessageRepository;
|
||||||
|
ServiceShareRepository = serviceShareRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Perform(User user)
|
public async Task Perform(User user)
|
||||||
@@ -83,6 +86,17 @@ public class UserDeleteService
|
|||||||
await ServiceService.Admin.Delete(service);
|
await ServiceService.Admin.Delete(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Service shares
|
||||||
|
var shares = ServiceShareRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.User.Id == user.Id)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var share in shares)
|
||||||
|
{
|
||||||
|
ServiceShareRepository.Delete(share);
|
||||||
|
}
|
||||||
|
|
||||||
// Transactions - Coupons - Gift codes
|
// Transactions - Coupons - Gift codes
|
||||||
var userWithDetails = UserRepository
|
var userWithDetails = UserRepository
|
||||||
.Get()
|
.Get()
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ 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<ServiceManageService>();
|
||||||
|
|
||||||
// Services / Ticketing
|
// Services / Ticketing
|
||||||
builder.Services.AddScoped<TicketService>();
|
builder.Services.AddScoped<TicketService>();
|
||||||
@@ -121,8 +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<ServiceAdminService>();
|
var serviceService = app.Services.GetRequiredService<ServiceTypeService>();
|
||||||
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());
|
|
||||||
|
serviceService.Register<DummyServiceImplementation>(ServiceType.Server);
|
||||||
|
|
||||||
await pluginService.RunPrePost(app);
|
await pluginService.RunPrePost(app);
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,16 @@
|
|||||||
@foreach (var prop in typeof(TForm).GetProperties())
|
@foreach (var prop in typeof(TForm).GetProperties())
|
||||||
{
|
{
|
||||||
<div class="col-md-@(Columns) col-12">
|
<div class="col-md-@(Columns) col-12">
|
||||||
<CascadingValue Name="Property" Value="prop">
|
@{
|
||||||
<CascadingValue Name="Data" Value="(object)Model">
|
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||||
@{
|
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
|
||||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
{
|
||||||
}
|
parameters.Add("Data", Model);
|
||||||
|
parameters.Add("Property", prop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@ComponentHelper.FromType(typeToCreate)
|
@rf
|
||||||
</CascadingValue>
|
|
||||||
</CascadingValue>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
return prop.GetValue(x) as string ?? "N/A";
|
return prop.GetValue(x) as string ?? "N/A";
|
||||||
});
|
});
|
||||||
|
|
||||||
<SmartDropdown @bind-Value="Binder.Class" DisplayFunc="displayFunc" SearchProp="searchFunc" Items="Items" />
|
<SmartDropdown @bind-Value="Binder.Class" DisplayFunc="displayFunc" SearchProp="searchFunc" Items="Items"/>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
return prop.GetValue(x) as string ?? "N/A";
|
return prop.GetValue(x) as string ?? "N/A";
|
||||||
});
|
});
|
||||||
|
|
||||||
<SmartSelect @bind-Value="Binder.Class" DisplayField="displayFunc" Items="Items" CanBeNull="true" />
|
<SmartSelect @bind-Value="Binder.Class" DisplayField="displayFunc" Items="Items" CanBeNull="true"/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,10 +119,10 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter(Name = "Data")]
|
[Parameter]
|
||||||
public object Data { get; set; }
|
public object Data { get; set; }
|
||||||
|
|
||||||
[CascadingParameter(Name = "Property")]
|
[Parameter]
|
||||||
public PropertyInfo Property { get; set; }
|
public PropertyInfo Property { get; set; }
|
||||||
|
|
||||||
private PropBinder<TProp> Binder;
|
private PropBinder<TProp> Binder;
|
||||||
|
|||||||
19
Moonlight/Shared/Components/Forms/DynamicTypedAutoForm.razor
Normal file
19
Moonlight/Shared/Components/Forms/DynamicTypedAutoForm.razor
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
@{
|
||||||
|
var typeToCreate = typeof(AutoForm<>).MakeGenericType(Model.GetType());
|
||||||
|
var rf = ComponentHelper.FromType(typeToCreate, parameter =>
|
||||||
|
{
|
||||||
|
parameter.Add("Model", Model);
|
||||||
|
parameter.Add("Columns", Columns);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@rf
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public object Model { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int Columns { get; set; } = 6;
|
||||||
|
}
|
||||||
@@ -109,14 +109,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Type</label>
|
<label class="form-label">Type</label>
|
||||||
<SmartEnumSelect @bind-Value="AddProductForm.Type"/>
|
<SmartEnumSelect @bind-Value="AddProductServiceType"/>
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Config</label>
|
|
||||||
<input @bind="AddProductForm.ConfigJson" class="form-control" type="text"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<DynamicTypedAutoForm Model="AddProductConfig" Columns="6"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
@@ -172,14 +171,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Type</label>
|
<label class="form-label">Type</label>
|
||||||
<SmartEnumSelect @bind-Value="EditProductForm.Type"/>
|
<SmartEnumSelect @bind-Value="EditProductServiceType"/>
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Config</label>
|
|
||||||
<input @bind="EditProductForm.ConfigJson" class="form-control" type="text"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<DynamicTypedAutoForm Model="EditProductConfig" Columns="6"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
@@ -231,6 +229,7 @@
|
|||||||
EditCategoryForm = Mapper.Map<EditCategoryForm>(EditCategory);
|
EditCategoryForm = Mapper.Map<EditCategoryForm>(EditCategory);
|
||||||
await EditCategoryModal.Show();
|
await EditCategoryModal.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EditCategorySubmit()
|
private async Task EditCategorySubmit()
|
||||||
{
|
{
|
||||||
EditCategory = Mapper.Map(EditCategory, EditCategoryForm);
|
EditCategory = Mapper.Map(EditCategory, EditCategoryForm);
|
||||||
@@ -250,17 +249,30 @@
|
|||||||
private SmartModal AddProductModal;
|
private SmartModal AddProductModal;
|
||||||
private AddProductForm AddProductForm = new();
|
private AddProductForm AddProductForm = new();
|
||||||
private Category[] Categories;
|
private Category[] Categories;
|
||||||
|
private object AddProductConfig = new();
|
||||||
|
|
||||||
|
private ServiceType AddProductServiceType
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (AddProductConfig.GetType() != StoreService.Admin.GetProductConfigType(value))
|
||||||
|
AddProductConfig = StoreService.Admin.CreateNewProductConfig(value);
|
||||||
|
|
||||||
|
AddProductForm.Type = value;
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
get => AddProductForm.Type;
|
||||||
|
}
|
||||||
|
|
||||||
public Task AddProductShow => AddProductModal.Show();
|
public Task AddProductShow => AddProductModal.Show();
|
||||||
|
|
||||||
private async Task AddProductSubmit()
|
private async Task AddProductSubmit()
|
||||||
{
|
{
|
||||||
await StoreService.Admin.AddProduct(
|
var product = await StoreService.Admin.AddProduct(
|
||||||
AddProductForm.Name,
|
AddProductForm.Name,
|
||||||
AddProductForm.Description,
|
AddProductForm.Description,
|
||||||
AddProductForm.Slug,
|
AddProductForm.Slug,
|
||||||
AddProductForm.Type,
|
AddProductForm.Type,
|
||||||
AddProductForm.ConfigJson,
|
|
||||||
product =>
|
product =>
|
||||||
{
|
{
|
||||||
product.Category = AddProductForm.Category;
|
product.Category = AddProductForm.Category;
|
||||||
@@ -271,6 +283,8 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
StoreService.Admin.SaveProductConfig(product, AddProductConfig);
|
||||||
|
|
||||||
await ToastService.Success("Successfully added product");
|
await ToastService.Success("Successfully added product");
|
||||||
await AddProductModal.Hide();
|
await AddProductModal.Hide();
|
||||||
|
|
||||||
@@ -285,10 +299,25 @@
|
|||||||
private SmartModal EditProductModal;
|
private SmartModal EditProductModal;
|
||||||
private EditProductForm EditProductForm = new();
|
private EditProductForm EditProductForm = new();
|
||||||
private Product EditProduct;
|
private Product EditProduct;
|
||||||
|
private object EditProductConfig = new();
|
||||||
|
|
||||||
|
private ServiceType EditProductServiceType
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (EditProductConfig.GetType() != StoreService.Admin.GetProductConfigType(value))
|
||||||
|
EditProductConfig = StoreService.Admin.CreateNewProductConfig(value);
|
||||||
|
|
||||||
|
EditProductForm.Type = value;
|
||||||
|
InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
get => EditProductForm.Type;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task EditProductShow(Product product)
|
public async Task EditProductShow(Product product)
|
||||||
{
|
{
|
||||||
EditProduct = product;
|
EditProduct = product;
|
||||||
|
EditProductConfig = StoreService.Admin.GetProductConfig(product);
|
||||||
|
|
||||||
EditProductForm = Mapper.Map<EditProductForm>(EditProduct);
|
EditProductForm = Mapper.Map<EditProductForm>(EditProduct);
|
||||||
await EditProductModal.Show();
|
await EditProductModal.Show();
|
||||||
@@ -299,6 +328,7 @@
|
|||||||
EditProduct = Mapper.Map(EditProduct, EditProductForm);
|
EditProduct = Mapper.Map(EditProduct, EditProductForm);
|
||||||
|
|
||||||
await StoreService.Admin.UpdateProduct(EditProduct);
|
await StoreService.Admin.UpdateProduct(EditProduct);
|
||||||
|
StoreService.Admin.SaveProductConfig(EditProduct, EditProductConfig);
|
||||||
|
|
||||||
await ToastService.Success("Successfully updated product");
|
await ToastService.Success("Successfully updated product");
|
||||||
await EditProductModal.Hide();
|
await EditProductModal.Hide();
|
||||||
|
|||||||
@@ -83,15 +83,16 @@ else
|
|||||||
@foreach (var prop in Properties)
|
@foreach (var prop in Properties)
|
||||||
{
|
{
|
||||||
<div class="col-md-6 col-12">
|
<div class="col-md-6 col-12">
|
||||||
<CascadingValue Name="Property" Value="prop">
|
@{
|
||||||
<CascadingValue Name="Data" Value="ModelToShow">
|
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||||
@{
|
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
|
||||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
{
|
||||||
}
|
parameters.Add("Data", ModelToShow);
|
||||||
|
parameters.Add("Property", prop);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@ComponentHelper.FromType(typeToCreate)
|
@rf
|
||||||
</CascadingValue>
|
|
||||||
</CascadingValue>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
|
|||||||
92
Moonlight/Shared/Views/Service/Index.razor
Normal file
92
Moonlight/Shared/Views/Service/Index.razor
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
@page "/service/{Id:int}/{Route?}"
|
||||||
|
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Database.Entities.Store
|
||||||
|
@using Moonlight.App.Services.ServiceManage
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Models.Abstractions
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
|
@inject Repository<Service> ServiceRepository
|
||||||
|
@inject ServiceService ServiceService
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
@inject PluginService PluginService
|
||||||
|
|
||||||
|
<LazyLoader Load="Load" ShowAsCard="true">
|
||||||
|
@if (Service == null)
|
||||||
|
{
|
||||||
|
<NotFoundAlert />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<CascadingValue Name="Service" Value="Service">
|
||||||
|
<CascadingValue Name="Implementation" Value="Implementation">
|
||||||
|
<CascadingValue Name="Route" Value="Route">
|
||||||
|
<CascadingValue Name="Pages" Value="ServiceUiPages">
|
||||||
|
@Implementation.GetUserLayout()
|
||||||
|
</CascadingValue>
|
||||||
|
</CascadingValue>
|
||||||
|
</CascadingValue>
|
||||||
|
</CascadingValue>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? Route { get; set; }
|
||||||
|
|
||||||
|
private Service? Service;
|
||||||
|
private ServiceImplementation Implementation;
|
||||||
|
private ServiceUiPage[] ServiceUiPages;
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
await lazyLoader.SetText("Requesting service");
|
||||||
|
|
||||||
|
// Load service with relational data
|
||||||
|
Service = ServiceRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Product)
|
||||||
|
.Include(x => x.Owner)
|
||||||
|
.FirstOrDefault(x => x.Id == Id);
|
||||||
|
|
||||||
|
if(Service == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check permissions
|
||||||
|
if (!await ServiceService.Manage.CheckAccess(Service, IdentityService.CurrentUser))
|
||||||
|
Service = null;
|
||||||
|
|
||||||
|
if (Service == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
await lazyLoader.SetText("Loading implementation");
|
||||||
|
|
||||||
|
Implementation = ServiceService.Type.Get(Service.Product.Type);
|
||||||
|
|
||||||
|
await lazyLoader.SetText("Building ui");
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
User = IdentityService.CurrentUser
|
||||||
|
});
|
||||||
|
|
||||||
|
// Done :D
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer p-3 text-center">
|
<div class="card-footer p-3 text-center">
|
||||||
<button class="btn btn-primary">Manage</button>
|
<a href="/service/@(service.Id)" class="btn btn-primary">Manage</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user