using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using RobotNet10.CustomConfiguration.DTOs; using RobotNet10.CustomConfiguration.DTOs.Requests; using RobotNet10.CustomConfiguration.Helpers; using RobotNet10.CustomConfiguration.Models; using RobotNet10.CustomConfiguration.Services; namespace RobotNet10.CustomConfiguration.Controllers; /// /// API controller cho quản lý configuration files /// [ApiController] [Route("api/configs")] [Authorize] public class ConfigController(IConfigService configService) : ControllerBase { private readonly IConfigService _configService = configService; // ========================================== // CONFIG FILE MANAGEMENT // ========================================== /// /// Tạo config mới /// [HttpPost] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> CreateConfig([FromBody] CreateConfigRequest request) { try { // Validate request if (string.IsNullOrWhiteSpace(request.ConfigType)) { return BadRequest(new { error = "ConfigType cannot be empty" }); } // Check if ConfigType already exists if (await _configService.ConfigTypeExistsAsync(request.ConfigType)) { return Conflict(new { error = $"Config with type '{request.ConfigType}' already exists" }); } // Convert DTOs to Models var variables = request.Variables.Select(MapDtoToVariable).ToList(); // Create config var config = await _configService.CreateConfigAsync( request.ConfigType, variables, request.Description ); var dto = MapConfigToDto(config); return CreatedAtAction( nameof(GetConfigById), new { id = config.Id }, dto); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } /// /// Lấy tất cả configs (metadata only) /// [HttpGet] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task>> GetAllConfigs([FromQuery] string? search) { var configs = await _configService.SearchConfigsAsync(search); var dtos = configs.Select(MapMetadataToDto).ToList(); return Ok(dtos); } /// /// Lấy config theo ID /// [HttpGet("{id:guid}")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetConfigById(Guid id) { var config = await _configService.GetConfigByIdAsync(id); if (config == null) { return NotFound(new { error = $"Config with ID '{id}' not found" }); } return Ok(MapConfigToDto(config)); } /// /// Lấy config theo ConfigType /// [HttpGet("by-type/{configType}")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetConfigByType(string configType) { if (string.IsNullOrWhiteSpace(configType)) { return BadRequest(new { error = "ConfigType cannot be empty" }); } var config = await _configService.GetConfigByTypeAsync(configType); if (config == null) { return NotFound(new { error = $"Config with type '{configType}' not found" }); } return Ok(MapConfigToDto(config)); } /// /// Kiểm tra ConfigType có tồn tại không /// [HttpGet("exists/{configType}")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] public async Task> ConfigTypeExists(string configType) { var exists = await _configService.ConfigTypeExistsAsync(configType); return Ok(exists); } /// /// Cập nhật config /// [HttpPut("{id:guid}")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> UpdateConfig(Guid id, [FromBody] UpdateConfigRequest request) { try { // Convert DTOs to Models if provided List? variables = null; if (request.Variables != null) { variables = [.. request.Variables.Select(MapDtoToVariable)]; } var config = await _configService.UpdateConfigAsync(id, variables, request.Description); return Ok(MapConfigToDto(config)); } catch (KeyNotFoundException) { return NotFound(new { error = $"Config with ID '{id}' not found" }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } /// /// Xóa config /// [HttpDelete("{id:guid}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteConfig(Guid id) { var deleted = await _configService.DeleteConfigAsync(id); if (!deleted) { return NotFound(new { error = $"Config with ID '{id}' not found" }); } return NoContent(); } // ========================================== // IMPORT/EXPORT // ========================================== /// /// Import config từ JSON file /// [HttpPost("import")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task> ImportConfig( IFormFile file, [FromForm] string configType, [FromForm] string? description = null) { try { if (file == null || file.Length == 0) { return BadRequest(new { error = "File is required" }); } if (!file.FileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { return BadRequest(new { error = "Only JSON files are supported" }); } if (string.IsNullOrWhiteSpace(configType)) { return BadRequest(new { error = "ConfigType is required" }); } // Validate ConfigType doesn't exist var exists = await _configService.ConfigTypeExistsAsync(configType); if (exists) { return Conflict(new { error = $"Config with type '{configType}' already exists" }); } using var stream = file.OpenReadStream(); // Import config with description (if provided, it overrides description from file) // Note: CreateConfigAsync inside ImportConfigFromJsonAsync already validates before saving var config = await _configService.ImportConfigFromJsonAsync(stream, configType, description); var dto = MapConfigToDto(config); return CreatedAtAction( nameof(GetConfigById), new { id = config.Id }, dto); } catch (InvalidOperationException ex) { return Conflict(new { error = ex.Message }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } /// /// Export config ra JSON file /// [HttpGet("{id:guid}/export")] [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task ExportConfig(Guid id) { try { var config = await _configService.GetConfigByIdAsync(id); if (config == null) { return NotFound(new { error = $"Config with ID '{id}' not found" }); } var stream = await _configService.ExportConfigToJsonAsync(config); // Ensure stream is at the beginning if (stream.CanSeek && stream.Position != 0) { stream.Position = 0; } // Ensure stream has data if (stream.Length == 0) { return StatusCode(500, new { error = "Export stream is empty" }); } return File(stream, "application/json", $"{config.ConfigType}.config.json"); } catch (KeyNotFoundException) { return NotFound(new { error = $"Config with ID '{id}' not found" }); } catch (Exception ex) { return StatusCode(500, new { error = $"Error exporting config: {ex.Message}" }); } } // ========================================== // VARIABLE MANAGEMENT // ========================================== /// /// Cập nhật giá trị của một variable /// [HttpPut("{id:guid}/variables/{variableName}")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> UpdateVariable( Guid id, string variableName, [FromBody] UpdateVariableRequest request) { if (string.IsNullOrWhiteSpace(variableName)) { return BadRequest(new { error = "Variable name cannot be empty" }); } try { var config = await _configService.UpdateVariableAsync(id, variableName, request.Value); return Ok(MapConfigToDto(config)); } catch (KeyNotFoundException ex) { return NotFound(new { error = ex.Message }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } /// /// Thêm variable mới vào config /// [HttpPost("{id:guid}/variables")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> AddVariable( Guid id, [FromBody] ConfigVariableDto variableDto) { if (string.IsNullOrWhiteSpace(variableDto.Name)) { return BadRequest(new { error = "Variable name cannot be empty" }); } if (string.IsNullOrWhiteSpace(variableDto.Type)) { return BadRequest(new { error = "Variable type cannot be empty" }); } try { var variable = MapDtoToVariable(variableDto); var config = await _configService.AddVariableAsync(id, variable); return Ok(MapConfigToDto(config)); } catch (KeyNotFoundException ex) { return NotFound(new { error = ex.Message }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } /// /// Xóa variable khỏi config /// [HttpDelete("{id:guid}/variables/{variableName}")] [ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> RemoveVariable(Guid id, string variableName) { if (string.IsNullOrWhiteSpace(variableName)) { return BadRequest(new { error = "Variable name cannot be empty" }); } try { var config = await _configService.RemoveVariableAsync(id, variableName); return Ok(MapConfigToDto(config)); } catch (KeyNotFoundException ex) { return NotFound(new { error = ex.Message }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } // ========================================== // MAPPING HELPERS // ========================================== private static ConfigFileDto MapConfigToDto(ConfigFile config) { return new ConfigFileDto { Id = config.Id, ConfigType = config.ConfigType, Variables = [.. config.Variables.Select(MapVariableToDto)], CreatedAt = config.CreatedAt, UpdatedAt = config.UpdatedAt, Description = config.Description }; } private static ConfigFileMetadataDto MapMetadataToDto(ConfigFileMetadata metadata) { return new ConfigFileMetadataDto { Id = metadata.Id, ConfigType = metadata.ConfigType, CreatedAt = metadata.CreatedAt, UpdatedAt = metadata.UpdatedAt, Description = metadata.Description }; } private static ConfigVariableDto MapVariableToDto(ConfigVariable variable) { return new ConfigVariableDto { Name = variable.Name, Type = variable.Type.ToString().ToLower(), Value = variable.Value, Min = variable.Min, Max = variable.Max, Roles = variable.Roles, EnumValues = variable.EnumValues }; } private static ConfigVariable MapDtoToVariable(ConfigVariableDto dto) { // Parse type string to enum var type = VariableTypeConverter.ParseType(dto.Type); return new ConfigVariable { Name = dto.Name, Type = type, Value = dto.Value, Min = dto.Min, Max = dto.Max, Roles = dto.Roles ?? string.Empty, EnumValues = dto.EnumValues }; } }