Started working on service implementation api
This commit is contained in:
parent
a1cd6b5cd9
commit
d55490dd51
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 RenderFragment FromType(Type type) => builder =>
|
||||
public static RenderFragment FromType(Type type, Action<Dictionary<string, object>>? buildAttributes = null) => builder =>
|
||||
{
|
||||
builder.OpenComponent(0, type);
|
||||
|
||||
if (buildAttributes != null)
|
||||
{
|
||||
Dictionary<string, object> parameters = new();
|
||||
buildAttributes.Invoke(parameters);
|
||||
builder.AddMultipleAttributes(1, parameters);
|
||||
}
|
||||
|
||||
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,
|
||||
AdminTickets = 1004,
|
||||
AdminCommunity = 1030,
|
||||
AdminServices = 1050,
|
||||
AdminStore = 1900,
|
||||
AdminViewExceptions = 1999,
|
||||
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
|
||||
{
|
||||
|
@ -9,4 +13,5 @@ public class PluginContext
|
|||
public WebApplication WebApplication { get; set; }
|
||||
public List<Action> PreInitTasks = 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 Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.Store;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Abstractions;
|
||||
using Moonlight.App.Plugins;
|
||||
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)
|
||||
{
|
||||
var result = new List<string>();
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.Store;
|
||||
using Moonlight.App.Database.Enums;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Models.Abstractions;
|
||||
using Moonlight.App.Repositories;
|
||||
|
||||
namespace Moonlight.App.Services.ServiceManage;
|
||||
|
||||
public class ServiceAdminService
|
||||
{
|
||||
public readonly Dictionary<ServiceType, ServiceActions> Actions = new();
|
||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||
private readonly ServiceTypeService ServiceTypeService;
|
||||
|
||||
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory)
|
||||
public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceTypeService serviceTypeService)
|
||||
{
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
ServiceTypeService = serviceTypeService;
|
||||
}
|
||||
|
||||
public async Task<Service> Create(User u, Product p, Action<Service>? modifyService = null)
|
||||
{
|
||||
if (!Actions.ContainsKey(p.Type))
|
||||
throw new DisplayException($"The product type {p.Type} is not registered");
|
||||
var impl = ServiceTypeService.Get(p);
|
||||
|
||||
// Load models in new scope
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
|
@ -49,8 +47,7 @@ public class ServiceAdminService
|
|||
var finishedService = serviceRepo.Add(service);
|
||||
|
||||
// Call the action for the logic behind the service type
|
||||
var actions = Actions[product.Type];
|
||||
await actions.Create(scope.ServiceProvider, finishedService);
|
||||
await impl.Actions.Create(scope.ServiceProvider, finishedService);
|
||||
|
||||
return finishedService;
|
||||
}
|
||||
|
@ -63,17 +60,15 @@ public class ServiceAdminService
|
|||
|
||||
var service = serviceRepo
|
||||
.Get()
|
||||
.Include(x => x.Product)
|
||||
.Include(x => x.Shares)
|
||||
.FirstOrDefault(x => x.Id == s.Id);
|
||||
|
||||
if (service == null)
|
||||
throw new DisplayException("Service does not exist anymore");
|
||||
|
||||
if (!Actions.ContainsKey(service.Product.Type))
|
||||
throw new DisplayException($"The product type {service.Product.Type} is not registered");
|
||||
var impl = ServiceTypeService.Get(service);
|
||||
|
||||
await Actions[service.Product.Type].Delete(scope.ServiceProvider, service);
|
||||
await impl.Actions.Delete(scope.ServiceProvider, service);
|
||||
|
||||
foreach (var share in service.Shares.ToArray())
|
||||
{
|
||||
|
@ -82,10 +77,4 @@ public class ServiceAdminService
|
|||
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
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.Exceptions;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.ServiceManage;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services.Store;
|
||||
|
||||
|
@ -9,11 +11,16 @@ public class StoreAdminService
|
|||
{
|
||||
private readonly Repository<Product> ProductRepository;
|
||||
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;
|
||||
CategoryRepository = categoryRepository;
|
||||
ServiceService = serviceService;
|
||||
}
|
||||
|
||||
public Task<Category> AddCategory(string name, string description, string slug)
|
||||
|
@ -31,8 +38,7 @@ public class StoreAdminService
|
|||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<Product> AddProduct(string name, string description, string slug, ServiceType type, string configJson,
|
||||
Action<Product>? modifyProduct = null)
|
||||
public Task<Product> AddProduct(string name, string description, string slug, ServiceType type, Action<Product>? modifyProduct = null)
|
||||
{
|
||||
if (ProductRepository.Get().Any(x => x.Slug == slug))
|
||||
throw new DisplayException("A product with that slug does already exist");
|
||||
|
@ -43,7 +49,7 @@ public class StoreAdminService
|
|||
Description = description,
|
||||
Slug = slug,
|
||||
Type = type,
|
||||
ConfigJson = configJson
|
||||
ConfigJson = "{}"
|
||||
};
|
||||
|
||||
if(modifyProduct != null)
|
||||
|
@ -68,7 +74,7 @@ public class StoreAdminService
|
|||
{
|
||||
if (ProductRepository.Get().Any(x => x.Id != product.Id && x.Slug == product.Slug))
|
||||
throw new DisplayException("A product with that slug does already exist");
|
||||
|
||||
|
||||
ProductRepository.Update(product);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
@ -96,4 +102,36 @@ public class StoreAdminService
|
|||
|
||||
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
|
||||
{
|
||||
private readonly Repository<Service> ServiceRepository;
|
||||
private readonly Repository<ServiceShare> ServiceShareRepository;
|
||||
private readonly Repository<Post> PostRepository;
|
||||
private readonly Repository<User> UserRepository;
|
||||
private readonly Repository<Transaction> TransactionRepository;
|
||||
|
@ -32,7 +33,8 @@ public class UserDeleteService
|
|||
Repository<CouponUse> couponUseRepository,
|
||||
Repository<Transaction> transactionRepository,
|
||||
Repository<Ticket> ticketRepository,
|
||||
Repository<TicketMessage> ticketMessageRepository)
|
||||
Repository<TicketMessage> ticketMessageRepository,
|
||||
Repository<ServiceShare> serviceShareRepository)
|
||||
{
|
||||
ServiceRepository = serviceRepository;
|
||||
ServiceService = serviceService;
|
||||
|
@ -44,6 +46,7 @@ public class UserDeleteService
|
|||
TransactionRepository = transactionRepository;
|
||||
TicketRepository = ticketRepository;
|
||||
TicketMessageRepository = ticketMessageRepository;
|
||||
ServiceShareRepository = serviceShareRepository;
|
||||
}
|
||||
|
||||
public async Task Perform(User user)
|
||||
|
@ -83,6 +86,17 @@ public class UserDeleteService
|
|||
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
|
||||
var userWithDetails = UserRepository
|
||||
.Get()
|
||||
|
|
|
@ -80,6 +80,8 @@ builder.Services.AddSingleton<AutoMailSendService>();
|
|||
// Services / ServiceManage
|
||||
builder.Services.AddScoped<ServiceService>();
|
||||
builder.Services.AddSingleton<ServiceAdminService>();
|
||||
builder.Services.AddSingleton<ServiceTypeService>();
|
||||
builder.Services.AddSingleton<ServiceManageService>();
|
||||
|
||||
// Services / Ticketing
|
||||
builder.Services.AddScoped<TicketService>();
|
||||
|
@ -121,8 +123,9 @@ app.MapControllers();
|
|||
// Auto start background services
|
||||
app.Services.GetRequiredService<AutoMailSendService>();
|
||||
|
||||
var serviceService = app.Services.GetRequiredService<ServiceAdminService>();
|
||||
await serviceService.RegisterAction(ServiceType.Server, new DummyActions());
|
||||
var serviceService = app.Services.GetRequiredService<ServiceTypeService>();
|
||||
|
||||
serviceService.Register<DummyServiceImplementation>(ServiceType.Server);
|
||||
|
||||
await pluginService.RunPrePost(app);
|
||||
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
@foreach (var prop in typeof(TForm).GetProperties())
|
||||
{
|
||||
<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 typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
|
||||
{
|
||||
parameters.Add("Data", Model);
|
||||
parameters.Add("Property", prop);
|
||||
});
|
||||
}
|
||||
|
||||
@ComponentHelper.FromType(typeToCreate)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
@rf
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -94,14 +94,14 @@
|
|||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.DisplayProp);
|
||||
return prop.GetValue(x) as string ?? "N/A";
|
||||
});
|
||||
|
||||
|
||||
var searchFunc = new Func<TProp, string>(x =>
|
||||
{
|
||||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.SelectorProp);
|
||||
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
|
||||
{
|
||||
|
@ -110,8 +110,8 @@
|
|||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.DisplayProp);
|
||||
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
|
||||
{
|
||||
[CascadingParameter(Name = "Data")]
|
||||
[Parameter]
|
||||
public object Data { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "Property")]
|
||||
[Parameter]
|
||||
public PropertyInfo Property { get; set; }
|
||||
|
||||
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 class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<SmartEnumSelect @bind-Value="AddProductForm.Type"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Config</label>
|
||||
<input @bind="AddProductForm.ConfigJson" class="form-control" type="text"/>
|
||||
<SmartEnumSelect @bind-Value="AddProductServiceType"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<DynamicTypedAutoForm Model="AddProductConfig" Columns="6"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
|
@ -172,14 +171,13 @@
|
|||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<SmartEnumSelect @bind-Value="EditProductForm.Type"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Config</label>
|
||||
<input @bind="EditProductForm.ConfigJson" class="form-control" type="text"/>
|
||||
<SmartEnumSelect @bind-Value="EditProductServiceType"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<DynamicTypedAutoForm Model="EditProductConfig" Columns="6"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
|
@ -217,7 +215,7 @@
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Edit category
|
||||
|
||||
private SmartModal EditCategoryModal;
|
||||
|
@ -231,6 +229,7 @@
|
|||
EditCategoryForm = Mapper.Map<EditCategoryForm>(EditCategory);
|
||||
await EditCategoryModal.Show();
|
||||
}
|
||||
|
||||
private async Task EditCategorySubmit()
|
||||
{
|
||||
EditCategory = Mapper.Map(EditCategory, EditCategoryForm);
|
||||
|
@ -239,7 +238,7 @@
|
|||
|
||||
await ToastService.Success("Successfully updated category");
|
||||
await EditCategoryModal.Hide();
|
||||
|
||||
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
|
||||
|
@ -250,17 +249,30 @@
|
|||
private SmartModal AddProductModal;
|
||||
private AddProductForm AddProductForm = new();
|
||||
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();
|
||||
|
||||
private async Task AddProductSubmit()
|
||||
{
|
||||
await StoreService.Admin.AddProduct(
|
||||
var product = await StoreService.Admin.AddProduct(
|
||||
AddProductForm.Name,
|
||||
AddProductForm.Description,
|
||||
AddProductForm.Slug,
|
||||
AddProductForm.Type,
|
||||
AddProductForm.ConfigJson,
|
||||
product =>
|
||||
{
|
||||
product.Category = AddProductForm.Category;
|
||||
|
@ -271,6 +283,8 @@
|
|||
}
|
||||
);
|
||||
|
||||
StoreService.Admin.SaveProductConfig(product, AddProductConfig);
|
||||
|
||||
await ToastService.Success("Successfully added product");
|
||||
await AddProductModal.Hide();
|
||||
|
||||
|
@ -279,16 +293,31 @@
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Edit product
|
||||
|
||||
private SmartModal EditProductModal;
|
||||
private EditProductForm EditProductForm = new();
|
||||
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)
|
||||
{
|
||||
EditProduct = product;
|
||||
EditProductConfig = StoreService.Admin.GetProductConfig(product);
|
||||
|
||||
EditProductForm = Mapper.Map<EditProductForm>(EditProduct);
|
||||
await EditProductModal.Show();
|
||||
|
@ -299,7 +328,8 @@
|
|||
EditProduct = Mapper.Map(EditProduct, EditProductForm);
|
||||
|
||||
await StoreService.Admin.UpdateProduct(EditProduct);
|
||||
|
||||
StoreService.Admin.SaveProductConfig(EditProduct, EditProductConfig);
|
||||
|
||||
await ToastService.Success("Successfully updated product");
|
||||
await EditProductModal.Hide();
|
||||
|
||||
|
|
|
@ -83,15 +83,16 @@ else
|
|||
@foreach (var prop in Properties)
|
||||
{
|
||||
<div class="col-md-6 col-12">
|
||||
<CascadingValue Name="Property" Value="prop">
|
||||
<CascadingValue Name="Data" Value="ModelToShow">
|
||||
@{
|
||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||
}
|
||||
|
||||
@ComponentHelper.FromType(typeToCreate)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
@{
|
||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
|
||||
{
|
||||
parameters.Add("Data", ModelToShow);
|
||||
parameters.Add("Property", prop);
|
||||
});
|
||||
}
|
||||
|
||||
@rf
|
||||
</div>
|
||||
}
|
||||
</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 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>
|
||||
|
|
Loading…
Reference in a new issue