448 lines
15 KiB
C#
448 lines
15 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// API controller cho quản lý configuration files
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/configs")]
|
|
[Authorize]
|
|
public class ConfigController(IConfigService configService) : ControllerBase
|
|
{
|
|
private readonly IConfigService _configService = configService;
|
|
|
|
// ==========================================
|
|
// CONFIG FILE MANAGEMENT
|
|
// ==========================================
|
|
|
|
/// <summary>
|
|
/// Tạo config mới
|
|
/// </summary>
|
|
[HttpPost]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status201Created)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lấy tất cả configs (metadata only)
|
|
/// </summary>
|
|
[HttpGet]
|
|
[ProducesResponseType(typeof(List<ConfigFileMetadataDto>), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<List<ConfigFileMetadataDto>>> GetAllConfigs([FromQuery] string? search)
|
|
{
|
|
var configs = await _configService.SearchConfigsAsync(search);
|
|
var dtos = configs.Select(MapMetadataToDto).ToList();
|
|
return Ok(dtos);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lấy config theo ID
|
|
/// </summary>
|
|
[HttpGet("{id:guid}")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lấy config theo ConfigType
|
|
/// </summary>
|
|
[HttpGet("by-type/{configType}")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kiểm tra ConfigType có tồn tại không
|
|
/// </summary>
|
|
[HttpGet("exists/{configType}")]
|
|
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<bool>> ConfigTypeExists(string configType)
|
|
{
|
|
var exists = await _configService.ConfigTypeExistsAsync(configType);
|
|
return Ok(exists);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cập nhật config
|
|
/// </summary>
|
|
[HttpPut("{id:guid}")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<ConfigFileDto>> UpdateConfig(Guid id, [FromBody] UpdateConfigRequest request)
|
|
{
|
|
try
|
|
{
|
|
// Convert DTOs to Models if provided
|
|
List<ConfigVariable>? 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Xóa config
|
|
/// </summary>
|
|
[HttpDelete("{id:guid}")]
|
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult> 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
|
|
// ==========================================
|
|
|
|
/// <summary>
|
|
/// Import config từ JSON file
|
|
/// </summary>
|
|
[HttpPost("import")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status201Created)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export config ra JSON file
|
|
/// </summary>
|
|
[HttpGet("{id:guid}/export")]
|
|
[ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
public async Task<ActionResult> 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
|
|
// ==========================================
|
|
|
|
/// <summary>
|
|
/// Cập nhật giá trị của một variable
|
|
/// </summary>
|
|
[HttpPut("{id:guid}/variables/{variableName}")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thêm variable mới vào config
|
|
/// </summary>
|
|
[HttpPost("{id:guid}/variables")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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 });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Xóa variable khỏi config
|
|
/// </summary>
|
|
[HttpDelete("{id:guid}/variables/{variableName}")]
|
|
[ProducesResponseType(typeof(ConfigFileDto), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<ActionResult<ConfigFileDto>> 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
|
|
};
|
|
}
|
|
}
|
|
|