diff --git a/Moonlight/App/Actions/Dummy/DummyActions.cs b/Moonlight/App/Actions/Dummy/DummyActions.cs index 1970ddcb..ba00b190 100644 --- a/Moonlight/App/Actions/Dummy/DummyActions.cs +++ b/Moonlight/App/Actions/Dummy/DummyActions.cs @@ -1,5 +1,6 @@ using Moonlight.App.Database.Entities.Store; using Moonlight.App.Models.Abstractions; +using Moonlight.App.Models.Abstractions.Services; namespace Moonlight.App.Actions.Dummy; diff --git a/Moonlight/App/Actions/Dummy/DummyConfig.cs b/Moonlight/App/Actions/Dummy/DummyConfig.cs new file mode 100644 index 00000000..6076d0b9 --- /dev/null +++ b/Moonlight/App/Actions/Dummy/DummyConfig.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; + +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; } +} \ No newline at end of file diff --git a/Moonlight/App/Actions/Dummy/DummyServiceDefinition.cs b/Moonlight/App/Actions/Dummy/DummyServiceDefinition.cs new file mode 100644 index 00000000..1d386a10 --- /dev/null +++ b/Moonlight/App/Actions/Dummy/DummyServiceDefinition.cs @@ -0,0 +1,25 @@ +using Moonlight.App.Actions.Dummy.Layouts; +using Moonlight.App.Actions.Dummy.Pages; +using Moonlight.App.Helpers; +using Moonlight.App.Models.Abstractions.Services; + +namespace Moonlight.App.Actions.Dummy; + +public class DummyServiceDefinition : ServiceDefinition +{ + public override ServiceActions Actions => new DummyActions(); + public override Type ConfigType => typeof(DummyConfig); + public override async Task BuildUserView(ServiceViewContext context) + { + context.Layout = ComponentHelper.FromType(); + + await context.AddPage("Demo", "/demo"); + } + + public override Task BuildAdminView(ServiceViewContext context) + { + context.Layout = ComponentHelper.FromType(); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight/App/Actions/Dummy/Layouts/DummyAdmin.razor b/Moonlight/App/Actions/Dummy/Layouts/DummyAdmin.razor new file mode 100644 index 00000000..0f7abae0 --- /dev/null +++ b/Moonlight/App/Actions/Dummy/Layouts/DummyAdmin.razor @@ -0,0 +1,5 @@ +

DummyAdmin

+ +@code { + +} \ No newline at end of file diff --git a/Moonlight/App/Actions/Dummy/Layouts/DummyUser.razor b/Moonlight/App/Actions/Dummy/Layouts/DummyUser.razor new file mode 100644 index 00000000..fb0c0ca5 --- /dev/null +++ b/Moonlight/App/Actions/Dummy/Layouts/DummyUser.razor @@ -0,0 +1,5 @@ +

DummyUser

+ +@code { + +} \ No newline at end of file diff --git a/Moonlight/App/Actions/Dummy/Pages/DummyPage.razor b/Moonlight/App/Actions/Dummy/Pages/DummyPage.razor new file mode 100644 index 00000000..b465c859 --- /dev/null +++ b/Moonlight/App/Actions/Dummy/Pages/DummyPage.razor @@ -0,0 +1,5 @@ +

DummyPage

+ +@code { + +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/ComponentHelper.cs b/Moonlight/App/Helpers/ComponentHelper.cs index 45871641..754bd6b9 100644 --- a/Moonlight/App/Helpers/ComponentHelper.cs +++ b/Moonlight/App/Helpers/ComponentHelper.cs @@ -4,9 +4,20 @@ namespace Moonlight.App.Helpers; public static class ComponentHelper { - public static RenderFragment FromType(Type type) => builder => + public static RenderFragment FromType(Type type, Action>? buildAttributes = null) => builder => { builder.OpenComponent(0, type); + + if (buildAttributes != null) + { + Dictionary parameters = new(); + buildAttributes.Invoke(parameters); + builder.AddMultipleAttributes(1, parameters); + } + builder.CloseComponent(); }; + + public static RenderFragment FromType(Action>? buildAttributes = null) where T : ComponentBase => + FromType(typeof(T), buildAttributes); } \ No newline at end of file diff --git a/Moonlight/App/Models/Abstractions/ServiceActions.cs b/Moonlight/App/Models/Abstractions/ServiceActions.cs deleted file mode 100644 index 6ae14d84..00000000 --- a/Moonlight/App/Models/Abstractions/ServiceActions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Moonlight.App.Database.Entities.Store; - -namespace Moonlight.App.Models.Abstractions; - -public abstract class ServiceActions -{ - public abstract Task Create(IServiceProvider provider, Service service); - public abstract Task Update(IServiceProvider provider, Service service); - public abstract Task Delete(IServiceProvider provider, Service service); -} \ No newline at end of file diff --git a/Moonlight/App/Models/Abstractions/Services/ServiceActions.cs b/Moonlight/App/Models/Abstractions/Services/ServiceActions.cs new file mode 100644 index 00000000..dfc010ed --- /dev/null +++ b/Moonlight/App/Models/Abstractions/Services/ServiceActions.cs @@ -0,0 +1,8 @@ +namespace Moonlight.App.Models.Abstractions.Services; + +public abstract class ServiceActions +{ + public abstract Task Create(IServiceProvider provider, Database.Entities.Store.Service service); + public abstract Task Update(IServiceProvider provider, Database.Entities.Store.Service service); + public abstract Task Delete(IServiceProvider provider, Database.Entities.Store.Service service); +} \ No newline at end of file diff --git a/Moonlight/App/Models/Abstractions/Services/ServiceDefinition.cs b/Moonlight/App/Models/Abstractions/Services/ServiceDefinition.cs new file mode 100644 index 00000000..0aafded1 --- /dev/null +++ b/Moonlight/App/Models/Abstractions/Services/ServiceDefinition.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Components; +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Models.Abstractions.Services; + +public abstract class ServiceDefinition +{ + // Config + public abstract ServiceActions Actions { get; } + public abstract Type ConfigType { get; } + + // Methods + public abstract Task BuildUserView(ServiceViewContext context); + public abstract Task BuildAdminView(ServiceViewContext context); +} \ No newline at end of file diff --git a/Moonlight/App/Models/Abstractions/Services/ServiceUiPage.cs b/Moonlight/App/Models/Abstractions/Services/ServiceUiPage.cs new file mode 100644 index 00000000..19a0ef0c --- /dev/null +++ b/Moonlight/App/Models/Abstractions/Services/ServiceUiPage.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components; + +namespace Moonlight.App.Models.Abstractions.Services; + +public class ServiceUiPage +{ + public string Name { get; set; } + public string Route { get; set; } + public string Icon { get; set; } + public RenderFragment Component { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Abstractions/Services/ServiceViewContext.cs b/Moonlight/App/Models/Abstractions/Services/ServiceViewContext.cs new file mode 100644 index 00000000..cc7bf805 --- /dev/null +++ b/Moonlight/App/Models/Abstractions/Services/ServiceViewContext.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Components; +using Moonlight.App.Database.Entities; +using Moonlight.App.Database.Entities.Store; +using Moonlight.App.Helpers; + +namespace Moonlight.App.Models.Abstractions.Services; + +public class ServiceViewContext +{ + // Meta + public Service Service { get; set; } + public User User { get; set; } + public Product Product { get; set; } + + // Content + public List Pages { get; set; } = new(); + public RenderFragment Layout { get; set; } + + public Task AddPage(string name, string route, string icon = "") where T : ComponentBase + { + Pages.Add(new() + { + Name = name, + Route = route, + Icon = icon, + Component = ComponentHelper.FromType() + }); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Enums/Permission.cs b/Moonlight/App/Models/Enums/Permission.cs index 677bf9d8..d342dde8 100644 --- a/Moonlight/App/Models/Enums/Permission.cs +++ b/Moonlight/App/Models/Enums/Permission.cs @@ -10,6 +10,7 @@ public enum Permission AdminUsersEdit = 1003, AdminTickets = 1004, AdminCommunity = 1030, + AdminServices = 1050, AdminStore = 1900, AdminViewExceptions = 1999, AdminRoot = 2000 diff --git a/Moonlight/App/Plugins/Contexts/PluginContext.cs b/Moonlight/App/Plugins/Contexts/PluginContext.cs index 90ba8324..afdf874b 100644 --- a/Moonlight/App/Plugins/Contexts/PluginContext.cs +++ b/Moonlight/App/Plugins/Contexts/PluginContext.cs @@ -1,4 +1,6 @@ -namespace Moonlight.App.Plugins.Contexts; +using Moonlight.App.Models.Abstractions.Services; + +namespace Moonlight.App.Plugins.Contexts; public class PluginContext { @@ -9,4 +11,6 @@ public class PluginContext public WebApplication WebApplication { get; set; } public List PreInitTasks = new(); public List PostInitTasks = new(); + public Action? BuildUserServiceView { get; set; } = null; + public Action? BuildAdminServiceView { get; set; } = null; } \ No newline at end of file diff --git a/Moonlight/App/Services/Interop/CookieService.cs b/Moonlight/App/Services/Interop/CookieService.cs index d8bf3a38..2aa0ce09 100644 --- a/Moonlight/App/Services/Interop/CookieService.cs +++ b/Moonlight/App/Services/Interop/CookieService.cs @@ -1,4 +1,5 @@ using Microsoft.JSInterop; +using Moonlight.App.Helpers; namespace Moonlight.App.Services.Interop; @@ -30,7 +31,9 @@ public class CookieService if(string.IsNullOrEmpty(cookiePart)) continue; - var cookieKeyValue = cookiePart.Split("="); + var cookieKeyValue = cookiePart.Split("=") + .Select(x => x.Trim()) // There may be spaces e.g. with the "AspNetCore.Culture" key + .ToArray(); if (cookieKeyValue.Length == 2) { diff --git a/Moonlight/App/Services/PluginService.cs b/Moonlight/App/Services/PluginService.cs index 81a3712d..6179460f 100644 --- a/Moonlight/App/Services/PluginService.cs +++ b/Moonlight/App/Services/PluginService.cs @@ -1,5 +1,9 @@ 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.Models.Abstractions.Services; using Moonlight.App.Plugins; using Moonlight.App.Plugins.Contexts; @@ -105,6 +109,26 @@ public class PluginService } } + public Task BuildUserServiceView(ServiceViewContext context) + { + foreach (var plugin in Plugins) + { + plugin.Context.BuildUserServiceView?.Invoke(context); + } + + return Task.CompletedTask; + } + + public Task BuildAdminServiceView(ServiceViewContext context) + { + foreach (var plugin in Plugins) + { + plugin.Context.BuildAdminServiceView?.Invoke(context); + } + + return Task.CompletedTask; + } + private string[] FindFiles(string dir) { var result = new List(); diff --git a/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs b/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs index 2fe7b46e..a22bf95d 100644 --- a/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs +++ b/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs @@ -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 Actions = new(); private readonly IServiceScopeFactory ServiceScopeFactory; + private readonly ServiceDefinitionService ServiceDefinitionService; - public ServiceAdminService(IServiceScopeFactory serviceScopeFactory) + public ServiceAdminService(IServiceScopeFactory serviceScopeFactory, ServiceDefinitionService serviceDefinitionService) { ServiceScopeFactory = serviceScopeFactory; + ServiceDefinitionService = serviceDefinitionService; } public async Task Create(User u, Product p, Action? modifyService = null) { - if (!Actions.ContainsKey(p.Type)) - throw new DisplayException($"The product type {p.Type} is not registered"); + var impl = ServiceDefinitionService.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 = ServiceDefinitionService.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; - } } \ No newline at end of file diff --git a/Moonlight/App/Services/ServiceManage/ServiceDefinitionService.cs b/Moonlight/App/Services/ServiceManage/ServiceDefinitionService.cs new file mode 100644 index 00000000..20c59d50 --- /dev/null +++ b/Moonlight/App/Services/ServiceManage/ServiceDefinitionService.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Moonlight.App.Database.Entities.Store; +using Moonlight.App.Database.Enums; +using Moonlight.App.Models.Abstractions; +using Moonlight.App.Models.Abstractions.Services; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services.ServiceManage; + +public class ServiceDefinitionService +{ + private readonly Dictionary ServiceImplementations = new(); + + private readonly IServiceScopeFactory ServiceScopeFactory; + + public ServiceDefinitionService(IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + } + + public void Register(ServiceType type) where T : ServiceDefinition + { + var impl = Activator.CreateInstance() as ServiceDefinition; + + 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 ServiceDefinition Get(Service s) + { + using var scope = ServiceScopeFactory.CreateScope(); + var serviceRepo = scope.ServiceProvider.GetRequiredService>(); + + var service = serviceRepo + .Get() + .Include(x => x.Product) + .First(x => x.Id == s.Id); + + return Get(service.Product); + } + + public ServiceDefinition Get(Product p) => Get(p.Type); + + public ServiceDefinition Get(ServiceType type) + { + if (!ServiceImplementations.ContainsKey(type)) + throw new ArgumentException($"No service implementation found for {type}"); + + return ServiceImplementations[type]; + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/ServiceManage/ServiceManageService.cs b/Moonlight/App/Services/ServiceManage/ServiceManageService.cs new file mode 100644 index 00000000..8858fcc6 --- /dev/null +++ b/Moonlight/App/Services/ServiceManage/ServiceManageService.cs @@ -0,0 +1,61 @@ +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 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>(); + + 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); + } + + public Task NeedsRenewal(Service s) + { + // We fetch the service in a new scope wo ensure that we are not caching + using var scope = ServiceScopeFactory.CreateScope(); + var serviceRepo = scope.ServiceProvider.GetRequiredService>(); + + var service = serviceRepo + .Get() + .First(x => x.Id == s.Id); + + return Task.FromResult(DateTime.UtcNow > service.RenewAt); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/ServiceManage/ServiceService.cs b/Moonlight/App/Services/ServiceManage/ServiceService.cs index c0e4a71c..24ab12df 100644 --- a/Moonlight/App/Services/ServiceManage/ServiceService.cs +++ b/Moonlight/App/Services/ServiceManage/ServiceService.cs @@ -13,6 +13,8 @@ public class ServiceService // This service is used for managing services and cr private readonly Repository UserRepository; public ServiceAdminService Admin => ServiceProvider.GetRequiredService(); + public ServiceDefinitionService Definition => ServiceProvider.GetRequiredService(); + public ServiceManageService Manage => ServiceProvider.GetRequiredService(); public ServiceService(IServiceProvider serviceProvider, Repository serviceRepository, Repository userRepository) { diff --git a/Moonlight/App/Services/Store/StoreAdminService.cs b/Moonlight/App/Services/Store/StoreAdminService.cs index 889708c2..5ebbb6b5 100644 --- a/Moonlight/App/Services/Store/StoreAdminService.cs +++ b/Moonlight/App/Services/Store/StoreAdminService.cs @@ -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 ProductRepository; private readonly Repository CategoryRepository; + private readonly ServiceService ServiceService; - public StoreAdminService(Repository productRepository, Repository categoryRepository) + public StoreAdminService( + Repository productRepository, + Repository categoryRepository, + ServiceService serviceService) { ProductRepository = productRepository; CategoryRepository = categoryRepository; + ServiceService = serviceService; } public Task AddCategory(string name, string description, string slug) @@ -31,8 +38,7 @@ public class StoreAdminService return Task.FromResult(result); } - public Task AddProduct(string name, string description, string slug, ServiceType type, string configJson, - Action? modifyProduct = null) + public Task AddProduct(string name, string description, string slug, ServiceType type, Action? 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.Definition.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.Definition.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); + } } \ No newline at end of file diff --git a/Moonlight/App/Services/Users/UserDeleteService.cs b/Moonlight/App/Services/Users/UserDeleteService.cs index 025802cf..6280c16e 100644 --- a/Moonlight/App/Services/Users/UserDeleteService.cs +++ b/Moonlight/App/Services/Users/UserDeleteService.cs @@ -12,6 +12,7 @@ namespace Moonlight.App.Services.Users; public class UserDeleteService { private readonly Repository ServiceRepository; + private readonly Repository ServiceShareRepository; private readonly Repository PostRepository; private readonly Repository UserRepository; private readonly Repository TransactionRepository; @@ -32,7 +33,8 @@ public class UserDeleteService Repository couponUseRepository, Repository transactionRepository, Repository ticketRepository, - Repository ticketMessageRepository) + Repository ticketMessageRepository, + Repository 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() diff --git a/Moonlight/App/Services/Utils/JwtService.cs b/Moonlight/App/Services/Utils/JwtService.cs index 41125119..3021c1c8 100644 --- a/Moonlight/App/Services/Utils/JwtService.cs +++ b/Moonlight/App/Services/Utils/JwtService.cs @@ -1,5 +1,6 @@ using JWT.Algorithms; using JWT.Builder; +using Moonlight.App.Helpers; using Newtonsoft.Json; namespace Moonlight.App.Services.Utils; @@ -47,6 +48,7 @@ public class JwtService } catch (Exception e) { + Logger.Warn(e.Message); return Task.FromResult(false); } } diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index b33d43e3..580ca593 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -80,6 +80,8 @@ builder.Services.AddSingleton(); // Services / ServiceManage builder.Services.AddScoped(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); // Services / Ticketing builder.Services.AddScoped(); @@ -121,8 +123,9 @@ app.MapControllers(); // Auto start background services app.Services.GetRequiredService(); -var serviceService = app.Services.GetRequiredService(); -await serviceService.RegisterAction(ServiceType.Server, new DummyActions()); +var serviceService = app.Services.GetRequiredService(); + +serviceService.Register(ServiceType.Server); await pluginService.RunPrePost(app); diff --git a/Moonlight/Shared/Components/Alerts/NeedsRenewalAlert.razor b/Moonlight/Shared/Components/Alerts/NeedsRenewalAlert.razor new file mode 100644 index 00000000..a21d6d4c --- /dev/null +++ b/Moonlight/Shared/Components/Alerts/NeedsRenewalAlert.razor @@ -0,0 +1,16 @@ +
+
+
+
+ Expired illustration +
+

+ This service has expired +

+
+ This service has expired and has to be renewed in order to manage it +
+ Go back to services +
+
+
\ No newline at end of file diff --git a/Moonlight/Shared/Components/Forms/AutoForm.razor b/Moonlight/Shared/Components/Forms/AutoForm.razor index 553c3d6e..3135cf1b 100644 --- a/Moonlight/Shared/Components/Forms/AutoForm.razor +++ b/Moonlight/Shared/Components/Forms/AutoForm.razor @@ -5,15 +5,16 @@ @foreach (var prop in typeof(TForm).GetProperties()) {
- - - @{ - 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
} diff --git a/Moonlight/Shared/Components/Forms/AutoProperty.razor b/Moonlight/Shared/Components/Forms/AutoProperty.razor index ae7616dd..202b542a 100644 --- a/Moonlight/Shared/Components/Forms/AutoProperty.razor +++ b/Moonlight/Shared/Components/Forms/AutoProperty.razor @@ -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(x => { var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.SelectorProp); return prop.GetValue(x) as string ?? "N/A"; }); - - + + } else { @@ -110,8 +110,8 @@ var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.DisplayProp); return prop.GetValue(x) as string ?? "N/A"; }); - - + + } } } @@ -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 Binder; diff --git a/Moonlight/Shared/Components/Forms/DynamicTypedAutoForm.razor b/Moonlight/Shared/Components/Forms/DynamicTypedAutoForm.razor new file mode 100644 index 00000000..25047634 --- /dev/null +++ b/Moonlight/Shared/Components/Forms/DynamicTypedAutoForm.razor @@ -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; +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Navigations/AdminStoreNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminStoreNavigation.razor index 1e8f8638..0adb6ee7 100644 --- a/Moonlight/Shared/Components/Navigations/AdminStoreNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/AdminStoreNavigation.razor @@ -16,6 +16,11 @@ Gifts + diff --git a/Moonlight/Shared/Components/Partials/Sidebar.razor b/Moonlight/Shared/Components/Partials/Sidebar.razor index eaec4317..e21c3e9b 100644 --- a/Moonlight/Shared/Components/Partials/Sidebar.razor +++ b/Moonlight/Shared/Components/Partials/Sidebar.razor @@ -84,6 +84,17 @@ + + - + - + - + +
+ +
- -
-
- - +
+
+ +
@@ -57,10 +65,19 @@ private Service[] MyServices; private Service[] SharedServices; + private Dictionary SharedRenewalStates = new(); private async Task Load(LazyLoader _) { + // Load all services MyServices = await ServiceService.Get(IdentityService.CurrentUser); SharedServices = await ServiceService.GetShared(IdentityService.CurrentUser); + + // Load all services renewal states + foreach (var service in SharedServices) + { + if(!SharedRenewalStates.ContainsKey(service)) + SharedRenewalStates.Add(service, await ServiceService.Manage.NeedsRenewal(service)); + } } } \ No newline at end of file diff --git a/Moonlight/wwwroot/svg/expired.svg b/Moonlight/wwwroot/svg/expired.svg new file mode 100644 index 00000000..9ac0f34d --- /dev/null +++ b/Moonlight/wwwroot/svg/expired.svg @@ -0,0 +1 @@ + \ No newline at end of file