diff --git a/Kyoo.Authentication/AuthenticationModule.cs b/Kyoo.Authentication/AuthenticationModule.cs index 9c7c3687..66cdf2fd 100644 --- a/Kyoo.Authentication/AuthenticationModule.cs +++ b/Kyoo.Authentication/AuthenticationModule.cs @@ -98,6 +98,7 @@ namespace Kyoo.Authentication services.Configure(_configuration.GetSection(PermissionOption.Path)); services.Configure(_configuration.GetSection(CertificateOption.Path)); services.Configure(_configuration.GetSection(AuthenticationOption.Path)); + services.AddConfiguration(AuthenticationOption.Path); List clients = new(); diff --git a/Kyoo.Common/Models/ConfigurationReference.cs b/Kyoo.Common/Models/ConfigurationReference.cs new file mode 100644 index 00000000..fab6f9d3 --- /dev/null +++ b/Kyoo.Common/Models/ConfigurationReference.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using JetBrains.Annotations; + +namespace Kyoo.Models +{ + /// + /// A class given information about a strongly typed configuration. + /// + public class ConfigurationReference + { + /// + /// The path of the resource (separated by ':') + /// + public string Path { get; } + + /// + /// The type of the resource. + /// + public Type Type { get; } + + + /// + /// Create a new using a given path and type. + /// This method does not create sub configuration resources. Please see + /// + /// The path of the resource (separated by ':' or "__") + /// The type of the resource + /// + public ConfigurationReference(string path, Type type) + { + Path = path; + Type = type; + } + + /// + /// Return the list of configuration reference a type has. + /// + /// + /// The base path of the type (separated by ':' or "__". If empty, it will start at root) + /// + /// The type of the object + /// The list of configuration reference a type has. + public static IEnumerable CreateReference(string path, [NotNull] Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + List ret = new() + { + new ConfigurationReference(path, type) + }; + + + if (!type.IsClass || type.AssemblyQualifiedName?.StartsWith("System") == true) + return ret; + + Type enumerable = Utility.GetGenericDefinition(type, typeof(IEnumerable<>)); + Type dictionary = Utility.GetGenericDefinition(type, typeof(IDictionary<,>)); + Type dictionaryKey = dictionary?.GetGenericArguments()[0]; + + if (dictionary != null && dictionaryKey == typeof(string)) + ret.AddRange(CreateReference($"{path}:{type.Name}:*", dictionary.GetGenericArguments()[1])); + else if (dictionary != null && dictionaryKey == typeof(int)) + ret.AddRange(CreateReference($"{path}:{type.Name}:", dictionary.GetGenericArguments()[1])); + else if (enumerable != null) + ret.AddRange(CreateReference($"{path}:{type.Name}:", enumerable.GetGenericArguments()[0])); + else + { + foreach (PropertyInfo child in type.GetProperties()) + ret.AddRange(CreateReference($"{path}:{child.Name}", child.PropertyType)); + } + + return ret; + } + + /// + /// Return the list of configuration reference a type has. + /// + /// + /// The base path of the type (separated by ':' or "__". If empty, it will start at root) + /// + /// The type of the object + /// The list of configuration reference a type has. + public static IEnumerable CreateReference(string path) + { + return CreateReference(path, typeof(T)); + } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Module.cs b/Kyoo.Common/Module.cs index ea87b604..e8e23035 100644 --- a/Kyoo.Common/Module.cs +++ b/Kyoo.Common/Module.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using Kyoo.Controllers; +using Kyoo.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -64,10 +66,20 @@ namespace Kyoo return services.AddRepository(lifetime); } - public static IServiceCollection AddConfiguration(this IServiceCollection services, IConfiguration config, string path) + /// + /// Add an editable configuration to the editable configuration list + /// + /// The service collection to edit + /// The root path of the editable configuration. It should not be a nested type. + /// The type of the configuration + /// The given service collection is returned. + public static IServiceCollection AddConfiguration(this IServiceCollection services, string path) + where T : class { - services.Configure(config.GetSection(path)); - services.AddSingleton(new ConfigReference(path, typeof(T))); + if (services.Any(x => x.ServiceType == typeof(T))) + return services; + foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path)) + services.AddSingleton(confRef); return services; } } diff --git a/Kyoo/Views/ConfigurationApi.cs b/Kyoo/Views/ConfigurationApi.cs index 2dfbc463..ef346979 100644 --- a/Kyoo/Views/ConfigurationApi.cs +++ b/Kyoo/Views/ConfigurationApi.cs @@ -1,3 +1,7 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Kyoo.Models; using Kyoo.Models.Permissions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -15,15 +19,22 @@ namespace Kyoo.Api /// /// The configuration to retrieve and edit. /// - private readonly IConfiguration _config; + private readonly IConfiguration _configuration; + + /// + /// The strongly typed list of options + /// + private readonly Dictionary _references; /// /// Create a new using the given configuration. /// - /// The configuration to use. - public ConfigurationApi(IConfiguration config) + /// The configuration to use. + /// The strongly typed option list. + public ConfigurationApi(IConfiguration configuration, IEnumerable references) { - _config = config; + _configuration = configuration; + _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); } /// @@ -35,7 +46,16 @@ namespace Kyoo.Api [Permission(nameof(ConfigurationApi), Kind.Admin)] public ActionResult GetConfiguration(string slug) { - return _config[slug]; + slug = slug.Replace("__", ":"); + // TODO handle lists and dictionaries. + if (!_references.TryGetValue(slug, out Type type)) + return NotFound(); + object ret = _configuration.GetValue(type, slug); + if (ret != null) + return ret; + object option = Activator.CreateInstance(type); + _configuration.Bind(slug, option); + return option; } } } \ No newline at end of file