Initial commit
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user