using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Manager.DTOs; using Manager.Helpers; using Manager.Interfaces.Models; using Manager.Services; using ManagerService.Data; using ManagerService.Data.SubSection; using ManagerService.DTOs; using ManagerService.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Mqtt.Client.AspNetCore.Services; using Newtonsoft.Json; using NSwag.Annotations; namespace ManagerService.Controllers { [Authorize(Policy = ManagerService.Service.Security.Policies.ContentEditor)] [ApiController, Route("api/[controller]")] [OpenApiTag("Configuration", Description = "Configuration management")] public class ConfigurationController : ControllerBase { private readonly MyInfoMateDbContext _myInfoMateDbContext; /*private ConfigurationDatabaseService _configurationService; private InstanceDatabaseService _instanceService; private SectionDatabaseService _sectionService; private ResourceDatabaseService _resourceService; private DeviceDatabaseService _deviceService;*/ private readonly ILogger _logger; private readonly IConfiguration _configuration; IHexIdGeneratorService idService = new HexIdGeneratorService(); public ConfigurationController(IConfiguration configuration, ILogger logger, MyInfoMateDbContext myInfoMateDbContext) { _logger = logger; _configuration = configuration; _myInfoMateDbContext = myInfoMateDbContext; } /// /// Get a list of all configuration (summary) /// /// id instance [AllowAnonymous] [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(string), 500)] [HttpGet] public ObjectResult Get([FromQuery] string instanceId) { try { List configurations = _myInfoMateDbContext.Configurations.Where(c => c.InstanceId == instanceId).ToList(); List configurationDTOs = new List(); foreach(var configuration in configurations) { List sectionIds = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == configuration.Id && !s.IsSubSection).Select(s => s.Id).ToList(); ConfigurationDTO configurationDTO = configuration.ToDTO(sectionIds); configurationDTOs.Add(configurationDTO); } return new OkObjectResult(configurationDTOs.OrderBy(c => c.dateCreation)); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Get Confuguration list by instanceId' pincode /// /// Code pin [Authorize(Policy = ManagerService.Service.Security.Policies.AppReadAccess)] [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpGet("byPin")] public ObjectResult GetConfigurationsByPinCode([FromQuery] string pinCode) { try { Instance instance = _myInfoMateDbContext.Instances.FirstOrDefault(i => i.PinCode == pinCode); if (instance == null) throw new KeyNotFoundException("None instance is linked to this pin code"); List configurations = _myInfoMateDbContext.Configurations.Where(c => c.InstanceId == instance.Id).ToList(); List configurationDTOs = new List(); foreach (var configuration in configurations) { List sectionIds = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == configuration.Id && !s.IsSubSection).Select(s => s.Id).ToList(); configurationDTOs.Add(configuration.ToDTO(sectionIds)); } return new OkObjectResult(configurationDTOs); } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Get a specific display configuration /// /// id configuration [AllowAnonymous] [ProducesResponseType(typeof(ConfigurationDTO), 200)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpGet("{id}")] public async Task GetDetailAsync(string id) { try { Configuration configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == id); if (configuration == null) throw new KeyNotFoundException("This configuration was not found"); List sectionIds = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == id && !s.IsSubSection).Select(s => s.Id).ToList(); try { var weatherSections = _myInfoMateDbContext.Sections.OfType() .Where(s => s.ConfigurationId == id && !s.IsSubSection) .ToList(); foreach (var weatherSection in weatherSections) { if (weatherSection.WeatherCity != null && weatherSection.WeatherCity.Length >= 2 && (weatherSection.WeatherUpdatedDate == null || weatherSection.WeatherUpdatedDate.Value.AddHours(3) < DateTimeOffset.Now)) // Update all 4 hours { // Call Openweather api with token from appSettings and update result with json var apiKey = _configuration.GetSection("OpenWeatherApiKey").Get(); if (apiKey != null && apiKey.Length > 0) { string url = $"http://api.openweathermap.org/geo/1.0/direct?q={weatherSection.WeatherCity}&limit=1&appid={apiKey}"; using (HttpClient client = new HttpClient()) { try { HttpResponseMessage response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); string responseBody = await response.Content.ReadAsStringAsync(); List cities = JsonConvert.DeserializeObject>(responseBody); if (cities.Count > 0) { double lat = cities[0].Lat; double lon = cities[0].Lon; //string onecallUrl = $"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&appid={apiKey}"; string callUrl = $"https://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&units=metric&appid={apiKey}"; HttpResponseMessage callResponse = await client.GetAsync(callUrl); callResponse.EnsureSuccessStatusCode(); string callResponseBody = await callResponse.Content.ReadAsStringAsync(); weatherSection.WeatherUpdatedDate = DateTimeOffset.Now.ToUniversalTime(); ; weatherSection.WeatherResult = callResponseBody; _myInfoMateDbContext.SaveChanges(); } else { Console.WriteLine("Aucune ville trouv�e."); } } catch (HttpRequestException e) { Console.WriteLine($"Une erreur s'est produite lors de la requ�te HTTP : {e.Message}"); } } } } } } catch (Exception e) { Console.WriteLine($"Une erreur s'est produite lors de la mise � jour des sections de type m�t�o : {e.Message}"); } return new OkObjectResult(configuration.ToDTO(sectionIds)); } catch (KeyNotFoundException ex) { return new NotFoundObjectResult(ex.Message) {}; } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Create a new configuration /// /// New configuration info [ProducesResponseType(typeof(ConfigurationDTO), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 409)] [ProducesResponseType(typeof(string), 500)] [HttpPost] public ObjectResult Create([FromBody] ConfigurationDTO newConfiguration) { try { if (newConfiguration == null) throw new ArgumentNullException("Configuration param is null"); // Todo add some verification ? Configuration configuration = new Configuration(); configuration.InstanceId = newConfiguration.instanceId; configuration.Label = newConfiguration.label; configuration.Title = new List(); configuration.ImageId = newConfiguration.imageId; configuration.ImageSource = newConfiguration.imageSource; configuration.PrimaryColor = newConfiguration.primaryColor; configuration.SecondaryColor = newConfiguration.secondaryColor; configuration.LoaderImageId = newConfiguration.loaderImageId; configuration.LoaderImageUrl = newConfiguration.loaderImageUrl; configuration.Languages = newConfiguration.languages; configuration.Title = LanguageInit.Init("Title", configuration.Languages); configuration.DateCreation = DateTime.Now.ToUniversalTime(); configuration.IsOffline = newConfiguration.isOffline; configuration.Id = idService.GenerateHexId(); _myInfoMateDbContext.Configurations.Add(configuration); //Configuration configurationCreated = _configurationService.Create(configuration); _myInfoMateDbContext.SaveChanges(); return new OkObjectResult(configuration.ToDTO(new List())); // Empty list } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message) {}; } catch (InvalidOperationException ex) { return new ConflictObjectResult(ex.Message) {}; } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Update a configuration /// /// Configuration to update [ProducesResponseType(typeof(ConfigurationDTO), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpPut] public ObjectResult Update([FromBody] ConfigurationDTO updatedConfiguration) { try { if (updatedConfiguration == null) throw new ArgumentNullException("configuration param is null"); Configuration configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == updatedConfiguration.id); if (configuration == null) throw new KeyNotFoundException("Configuration does not exist"); // Todo add some verification ? configuration.InstanceId = updatedConfiguration.instanceId; configuration.Label = updatedConfiguration.label; configuration.Title = updatedConfiguration.title; configuration.ImageId = updatedConfiguration.imageId; configuration.ImageSource = updatedConfiguration.imageSource; configuration.PrimaryColor = updatedConfiguration.primaryColor; configuration.SecondaryColor = updatedConfiguration.secondaryColor; configuration.Languages = updatedConfiguration.languages; configuration.IsOffline = updatedConfiguration.isOffline; configuration.LoaderImageId = updatedConfiguration.loaderImageId; configuration.LoaderImageUrl = updatedConfiguration.loaderImageUrl; //Configuration configurationModified = _configurationService.Update(updatedConfiguration.id, configuration); _myInfoMateDbContext.SaveChanges(); MqttClientService.PublishMessage($"config/{configuration.Id}", JsonConvert.SerializeObject(new PlayerMessageDTO() { configChanged = true })); List sectionIds = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == configuration.Id && !s.IsSubSection).Select(s => s.Id).ToList(); // _sectionService.GetAllIdsFromConfiguration(configuration.Id); return new OkObjectResult(configuration.ToDTO(sectionIds)); } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message) {}; } catch (KeyNotFoundException ex) { return new NotFoundObjectResult(ex.Message) {}; } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Delete a configuration /// /// Id of configuration to delete [ProducesResponseType(typeof(string), 202)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpDelete("{id}")] public ObjectResult Delete(string id) { try { if (id == null) throw new ArgumentNullException("Configuration param is null"); var configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == id); if (configuration == null) throw new KeyNotFoundException("Configuration does not exist"); _myInfoMateDbContext.Remove(configuration); _myInfoMateDbContext.SaveChanges(); // Delete config for all devices List devices = _myInfoMateDbContext.Devices.Where(d => d.ConfigurationId == id).ToList(); foreach (var device in devices) { device.Configuration = null; device.ConfigurationId = null; _myInfoMateDbContext.SaveChanges(); //_deviceService.Update(device.Id, device); } MqttClientService.PublishMessage($"config/{id}", JsonConvert.SerializeObject(new PlayerMessageDTO() { configChanged = true, isDeleted = true })); return new ObjectResult("The configuration has been deleted") { StatusCode = 202 }; } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message) { }; } catch (KeyNotFoundException ex) { return new NotFoundObjectResult(ex.Message) { }; } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Export a configuration /// /// Id of configuration to export /// Language to export [Authorize(Policy = ManagerService.Service.Security.Policies.AppReadAccess)] [ProducesResponseType(typeof(FileContentResult), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 500)] [HttpGet("{id}/export")] public FileContentResult Export(string id, [FromQuery] string language) { try { if (id == null) throw new ArgumentNullException("Configuration param is null"); Configuration configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == id); if (configuration == null) throw new KeyNotFoundException("Configuration does not exist"); // Les entités, pas seulement leurs DTO : la collecte des ressources passe // par GetReferencedResourceIds, qui vit sur le sous-type. List
sections = _myInfoMateDbContext.Sections.Where(s => s.ConfigurationId == configuration.Id).ToList(); List sectionDTOs = sections.Select(s => s.ToDTO()).ToList(); List resourceDTOs = new List(); if (configuration.ImageId != null) { addResourceToList(resourceDTOs, configuration.ImageId); } if (configuration.LoaderImageId != null) { addResourceToList(resourceDTOs, configuration.LoaderImageId); } foreach (var section in sections) { // Remplace 157 lignes de `switch` commenté qui n'ont jamais tourné : // une visite téléchargée n'embarquait que l'image de la configuration, // celle du loader et l'image de chaque section — ni contenus d'articles, // ni audios, ni icônes de carte, ni images de quiz. // `GetReferencedResourceIds` est déjà implémentée sur les 13 sous-types // et connaît chacun ses propres médias ; c'est aussi elle qui garantit // qu'un nouveau type de section n'ouvre pas un trou silencieux ici. foreach (var resourceId in section.GetReferencedResourceIds(language)) addResourceToList(resourceDTOs, resourceId); } ExportConfigurationDTO toDownload = configuration.ToExportDTO(sectionDTOs, resourceDTOs); string jsonString = JsonConvert.SerializeObject(toDownload); var fileName = $"{configuration.Label}.json"; var mimeType = "application/json"; var fileBytes = Encoding.UTF8.GetBytes(jsonString); return new FileContentResult(fileBytes, mimeType) { FileDownloadName = fileName }; } catch (ArgumentNullException ex) { return null; //return new BadRequestObjectResult(ex.Message) { }; } catch (KeyNotFoundException ex) { return null; //return new NotFoundObjectResult(ex.Message) { }; } catch (Exception ex) { return null; //return new ObjectResult(ex.Message) { StatusCode = 500 }; } } /// /// Import a configuration /// /// Configuration to import [ProducesResponseType(typeof(string), 202)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 404)] [ProducesResponseType(typeof(string), 409)] [ProducesResponseType(typeof(string), 500)] [HttpPost("import")] public ObjectResult Import([FromBody] ExportConfigurationDTO exportConfiguration) { try { if (exportConfiguration == null) throw new ArgumentNullException("File to import is null"); Configuration configuration = _myInfoMateDbContext.Configurations.FirstOrDefault(c => c.Id == exportConfiguration.id); if (configuration != null) throw new InvalidOperationException("Configuration already exist in the system"); configuration = new Configuration(); configuration.Id = exportConfiguration.id; configuration.InstanceId = exportConfiguration.instanceId; configuration.Label = exportConfiguration.label; configuration.Title = exportConfiguration.title; configuration.ImageId = exportConfiguration.imageId; configuration.ImageSource = exportConfiguration.imageSource; if (configuration.ImageId != null) { createResource(exportConfiguration.resources.Where(r => r.id == configuration.ImageId).FirstOrDefault()); } configuration.DateCreation = exportConfiguration.dateCreation; configuration.PrimaryColor = exportConfiguration.primaryColor; configuration.SecondaryColor = exportConfiguration.secondaryColor; configuration.Languages = exportConfiguration.languages; configuration.IsOffline = exportConfiguration.isOffline; configuration.LoaderImageId = exportConfiguration.loaderImageId; configuration.LoaderImageUrl = exportConfiguration.loaderImageUrl; if (configuration.LoaderImageId != null) { createResource(exportConfiguration.resources.Where(r => r.id == configuration.LoaderImageId).FirstOrDefault()); } _myInfoMateDbContext.Configurations.Add(configuration); //_configurationService.Create(configuration); var sectionsAlreadyInDB = _myInfoMateDbContext.Sections.Where(s => !exportConfiguration.sections.Select(s => s.id).Contains(s.Id)).Select(s => s.Id).ToList(); // Toutes les ressources de la charge, en une passe. `createResource` est // idempotente, donc les quelques appels ciblés qui subsistent plus haut sont // sans effet — et un nouveau type de section ne peut plus ouvrir de trou ici. foreach (var resourceExport in exportConfiguration.resources ?? new List()) createResource(resourceExport); foreach (var section in exportConfiguration.sections.Where(s => !sectionsAlreadyInDB.Contains(s.id))) { Section newSection = SectionFactory.CreateEmpty(section.type); newSection.Id = section.id; newSection.InstanceId = section.instanceId; newSection.Label = section.label; newSection.Title = section.title; newSection.Description = section.description; newSection.Order = section.order.GetValueOrDefault(); // if one day we can use same section in multiple configuration, need to change that newSection.Type = section.type; newSection.ImageId = section.imageId; newSection.ImageSource = section.imageSource; newSection.ConfigurationId = section.configurationId; newSection.IsSubSection = section.isSubSection; newSection.ParentId = section.parentId; //newSection.Data = section.data; newSection.DateCreation = section.dateCreation.Value; newSection.IsBeacon = section.isBeacon; newSection.BeaconId = section.beaconId; newSection.Latitude = section.latitude; newSection.Longitude = section.longitude; newSection.MeterZoneGPS = section.meterZoneGPS; if (newSection.ImageId != null) { createResource(exportConfiguration.resources.Where(r => r.id == newSection.ImageId).FirstOrDefault()); } _myInfoMateDbContext.Sections.Add(newSection); //_sectionService.Create(newSection); // Le `switch` de 126 lignes qui vivait ici cherchait, section par // section, quelles ressources de la charge créer — et il était commenté, // donc seule l'image de la section arrivait. Remplacé par la boucle // unique plus haut : l'export embarque désormais toutes les ressources // référencées, l'import n'a plus à les redécouvrir. } return new ObjectResult("The configuration has been successfully imported") { StatusCode = 202 }; } catch (ArgumentNullException ex) { return new BadRequestObjectResult(ex.Message) { }; } catch (KeyNotFoundException ex) { return new NotFoundObjectResult(ex.Message) { }; } catch (InvalidOperationException ex) { return new ConflictObjectResult(ex.Message) { }; } catch (Exception ex) { return new ObjectResult(ex.Message) { StatusCode = 500 }; } } private void createResource(ResourceDTO resourceExport) { if (resourceExport != null) { Resource resource = new Resource(); resource.Id = resourceExport.id; resource.InstanceId = resourceExport.instanceId; resource.Type = resourceExport.type; resource.Label = resourceExport.label; resource.DateCreation = resourceExport.dateCreation; //resource.Data = resourceExport.data; var resourceInDb = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == resourceExport.id); if (resourceInDb == null) _myInfoMateDbContext.Resources.Add(resource); //_resourceService.Create(resource); } } private List addResourceToList(List resourceDTOs, string resourceId) { if (!resourceDTOs.Select(r => r.id).Contains(resourceId)) { Resource resource = _myInfoMateDbContext.Resources.FirstOrDefault(r => r.Id == resourceId); if (resource != null && !resourceDTOs.Any(r => r.id == resource.Id)) { resourceDTOs.Add(resource.ToDTO()); } } return resourceDTOs; } } }