using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration.Attributes; using SharedLibraryCore.Configuration.Validation; using SharedLibraryCore.Interfaces; using System.Linq; using System.Reflection; using System.Threading.Tasks; using WebfrontCore.ViewModels; namespace WebfrontCore.Controllers { [Authorize] public class ConfigurationController : BaseController { private readonly ApplicationConfigurationValidator _validator; public ConfigurationController(IManager manager) : base(manager) { _validator = new ApplicationConfigurationValidator(); } /// /// Endpoint to get the current configuration view /// /// public IActionResult Edit() { if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) { return Unauthorized(); } return View("Index", Manager.GetApplicationSettings().Configuration()); } /// /// Endpoint for the save action /// /// bound configuration /// [HttpPost] public async Task Save(ApplicationConfiguration newConfiguration) { // todo: make this authorization middleware instead of these checks if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) { return Unauthorized(); } CleanConfiguration(newConfiguration); var validationResult = _validator.Validate(newConfiguration); if (validationResult.IsValid) { var currentConfiguration = Manager.GetApplicationSettings().Configuration(); CopyConfiguration(newConfiguration, currentConfiguration); await Manager.GetApplicationSettings().Save(); return Ok(new { message = new[] { Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVED"] } }); } else { return BadRequest(new { message = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVE_FAILED"], errors = new[] { validationResult.Errors.Select(_error => _error.ErrorMessage) } }); } } /// /// Cleans the configuration by removing empty items from from the array /// /// private void CleanConfiguration(ApplicationConfiguration newConfiguration) { void cleanProperties(object config) { foreach (var property in config.GetType() .GetProperties().Where(_prop => _prop.CanWrite)) { var newPropValue = property.GetValue(config); if (newPropValue is ServerConfiguration[] serverConfig) { foreach (var c in serverConfig) { cleanProperties(c); } } // this clears out any null or empty items in the string array if (newPropValue is string[] configArray) { newPropValue = configArray.Where(_str => !string.IsNullOrWhiteSpace(_str)).ToArray(); } property.SetValue(config, newPropValue); } } cleanProperties(newConfiguration); } /// /// Copies required config fields from new to old /// /// Source config /// Destination config private void CopyConfiguration(ApplicationConfiguration newConfiguration, ApplicationConfiguration oldConfiguration) { foreach (var property in newConfiguration.GetType() .GetProperties().Where(_prop => _prop.CanWrite)) { var newPropValue = property.GetValue(newConfiguration); bool isPropNullArray = property.PropertyType.IsArray && newPropValue == null; // this prevents us from setting a null array as that could screw reading up if (!ShouldIgnoreProperty(property) && !isPropNullArray) { property.SetValue(oldConfiguration, newPropValue); } } } /// /// Generates the partial view for a new list item /// /// name of the property the input element is generated for /// how many items exist already /// if it's a server property, which one /// public IActionResult GetNewListItem(string propertyName, int itemCount, int serverIndex = -1) { if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) { return Unauthorized(); } // todo: maybe make this cleaner in the future if (propertyName.StartsWith("Servers") && serverIndex < 0) { return PartialView("_ServerItem", new ApplicationConfiguration() { Servers = Enumerable.Repeat(new ServerConfiguration(), itemCount + 1).ToArray() }); } var model = new BindingHelper() { Properties = propertyName.Split("."), ItemIndex = itemCount, ParentItemIndex = serverIndex }; return PartialView("_ListItem", model); } /// /// Indicates if the property should be ignored when cleaning/copying from one config to another /// /// property info of the current property /// private bool ShouldIgnoreProperty(PropertyInfo info) => (info.GetCustomAttributes(false) .Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore)) .FirstOrDefault() as ConfigurationIgnore) != null; } }