Started working on service implementation api

This commit is contained in:
Baumgartner Marcel
2023-11-14 17:54:15 +01:00
parent a1cd6b5cd9
commit d55490dd51
26 changed files with 494 additions and 69 deletions

View 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; }
}

View 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>();
}
}

View File

@@ -0,0 +1,5 @@
<h3>DummyAdmin</h3>
@code {
}

View File

@@ -0,0 +1,5 @@
<h3>DummyUser</h3>
@code {
}

View File

@@ -0,0 +1,5 @@
<h3>DummyPage</h3>
@code {
}

View File

@@ -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();
}; };
} }

View 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);
}

View 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; }
}

View File

@@ -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

View File

@@ -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;
} }

View 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; }
}

View File

@@ -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>();

View File

@@ -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;
}
} }

View 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);
}
}

View File

@@ -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)
{ {

View 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];
}
}

View File

@@ -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);
}
} }

View File

@@ -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()

View File

@@ -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);

View File

@@ -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 typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
{
parameters.Add("Data", Model);
parameters.Add("Property", prop);
});
} }
@ComponentHelper.FromType(typeToCreate) @rf
</CascadingValue>
</CascadingValue>
</div> </div>
} }

View File

@@ -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;

View 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;
}

View File

@@ -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();

View File

@@ -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 typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
var rf = ComponentHelper.FromType(typeToCreate, parameters =>
{
parameters.Add("Data", ModelToShow);
parameters.Add("Property", prop);
});
} }
@ComponentHelper.FromType(typeToCreate) @rf
</CascadingValue>
</CascadingValue>
</div> </div>
} }
</LazyLoader> </LazyLoader>

View 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
}
}

View File

@@ -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>