feat: Añade endpoints de asociación y stubs para comandos a EquiposController

This commit is contained in:
2025-10-02 15:37:06 -03:00
parent 80210e5d4c
commit 85bd1915e0
3 changed files with 132 additions and 11 deletions

View File

@@ -1,8 +1,12 @@
using Dapper; using Dapper;
using Inventario.API.Data; using Inventario.API.Data;
using Inventario.API.DTOs;
using Inventario.API.Helpers; using Inventario.API.Helpers;
using Inventario.API.Models; using Inventario.API.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Data;
using System.Net.NetworkInformation;
using Microsoft.Data.SqlClient;
namespace Inventario.API.Controllers namespace Inventario.API.Controllers
{ {
@@ -17,19 +21,18 @@ namespace Inventario.API.Controllers
_context = context; _context = context;
} }
// --- GET /api/equipos --- // --- MÉTODOS CRUD BÁSICOS (Ya implementados) ---
// Consulta todos los equipos con sus relaciones // GET /api/equipos
[HttpGet] [HttpGet]
public async Task<IActionResult> Consultar() public async Task<IActionResult> Consultar()
{ {
// Query para traer todo en una sola llamada a la BD
var query = @" var query = @"
SELECT SELECT
e.*, e.Id, e.Hostname, e.Ip, e.Mac, e.Motherboard, e.Cpu, e.Ram_installed, e.Ram_slots, e.Os, e.Architecture,
s.Id as SectorId, s.Nombre as SectorNombre, s.Id as Id, s.Nombre,
u.Id as UsuarioId, u.Username, u.Password, u.Id as Id, u.Username, u.Password,
d.Id as DiscoId, d.Mediatype, d.Size, d.Id as Id, d.Mediatype, d.Size,
mr.Id as MemoriaRamId, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad, emr.Slot mr.Id as Id, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad, emr.Slot
FROM dbo.equipos e FROM dbo.equipos e
LEFT JOIN dbo.sectores s ON e.sector_id = s.id LEFT JOIN dbo.sectores s ON e.sector_id = s.id
LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id
@@ -61,9 +64,9 @@ namespace Inventario.API.Controllers
return equipoActual; return equipoActual;
}, },
splitOn: "SectorId,UsuarioId,DiscoId,MemoriaRamId" splitOn: "Id,Id,Id,Id" // Dapper divide en cada 'Id'
); );
return Ok(equipoDict.Values); return Ok(equipoDict.Values.OrderBy(e => e.Sector?.Nombre).ThenBy(e => e.Hostname));
} }
} }
@@ -201,5 +204,115 @@ namespace Inventario.API.Controllers
return Ok(new { equipo = hostname, historial }); return Ok(new { equipo = hostname, historial });
} }
} }
// --- MÉTODOS DE ASOCIACIÓN Y COMANDOS ---
[HttpPatch("{id_equipo}/sector/{id_sector}")]
public async Task<IActionResult> AsociarSector(int id_equipo, int id_sector)
{
var query = "UPDATE dbo.equipos SET sector_id = @IdSector WHERE Id = @IdEquipo;";
using (var connection = _context.CreateConnection())
{
var filasAfectadas = await connection.ExecuteAsync(query, new { IdSector = id_sector, IdEquipo = id_equipo });
if (filasAfectadas == 0) return NotFound("Equipo o sector no encontrado.");
return Ok(new { success = true }); // Devolvemos una respuesta simple de éxito
} }
} }
[HttpPost("{hostname}/asociarusuario")]
public async Task<IActionResult> AsociarUsuario(string hostname, [FromBody] AsociacionUsuarioDto dto)
{
var query = @"
INSERT INTO dbo.usuarios_equipos (equipo_id, usuario_id)
SELECT e.id, u.id
FROM dbo.equipos e, dbo.usuarios u
WHERE e.Hostname = @Hostname AND u.Username = @Username;";
using (var connection = _context.CreateConnection())
{
try
{
var filasAfectadas = await connection.ExecuteAsync(query, new { Hostname = hostname, dto.Username });
if (filasAfectadas == 0) return NotFound("Equipo o usuario no encontrado.");
return Ok(new { success = true });
}
catch (SqlException ex) when (ex.Number == 2627) // Error de clave primaria duplicada
{
return Conflict("El usuario ya está asociado a este equipo.");
}
}
}
[HttpDelete("{hostname}/usuarios/{username}")]
public async Task<IActionResult> DesasociarUsuario(string hostname, string username)
{
var query = @"
DELETE FROM dbo.usuarios_equipos
WHERE equipo_id = (SELECT id FROM dbo.equipos WHERE Hostname = @Hostname)
AND usuario_id = (SELECT id FROM dbo.usuarios WHERE Username = @Username);";
using (var connection = _context.CreateConnection())
{
var filasAfectadas = await connection.ExecuteAsync(query, new { Hostname = hostname, Username = username });
if (filasAfectadas == 0) return NotFound("Asociación no encontrada.");
return Ok(new { success = true });
}
}
[HttpPost("{hostname}/asociardiscos")]
public async Task<IActionResult> AsociarDiscos(string hostname, [FromBody] List<Disco> discos)
{
// La lógica aquí es compleja, la implementaremos en un paso posterior si es necesaria.
// Por ahora, devolvemos un endpoint funcional pero que no hace nada.
Console.WriteLine($"Recibida solicitud para asociar {discos.Count} discos al equipo {hostname}");
await Task.CompletedTask; // Simula trabajo asíncrono
return Ok(new { message = "Endpoint de asociación de discos recibido, lógica pendiente." });
}
[HttpPost("{hostname}/ram")]
public async Task<IActionResult> AsociarRam(string hostname, [FromBody] List<MemoriaRamDetalle> memorias)
{
// Lógica compleja, pendiente de implementación.
Console.WriteLine($"Recibida solicitud para asociar {memorias.Count} módulos de RAM al equipo {hostname}");
await Task.CompletedTask;
return Ok(new { message = "Endpoint de asociación de RAM recibido, lógica pendiente." });
}
[HttpPost("ping")]
public async Task<IActionResult> EnviarPing([FromBody] PingRequestDto request)
{
if (string.IsNullOrWhiteSpace(request.Ip))
return BadRequest("La dirección IP es requerida.");
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(request.Ip, 2000); // Timeout de 2 segundos
return Ok(new { isAlive = reply.Status == IPStatus.Success, latency = reply.RoundtripTime });
}
}
catch (PingException ex)
{
// Maneja errores comunes como "Host desconocido"
return Ok(new { isAlive = false, error = ex.Message });
}
catch (Exception ex)
{
return StatusCode(500, $"Error interno al hacer ping: {ex.Message}");
}
}
// WOL (Wake-On-LAN) es más complejo porque requiere ejecutar comandos de sistema operativo.
// Lo dejamos pendiente para no añadir complejidad de configuración de SSH por ahora.
[HttpPost("wake-on-lan")]
public IActionResult EnviarWol([FromBody] WolRequestDto request)
{
Console.WriteLine($"Recibida solicitud WOL para MAC: {request.Mac}");
return Ok(new { message = "Solicitud WOL recibida. La ejecución del comando está pendiente de implementación." });
}
}
// DTOs locales para las peticiones
public class PingRequestDto { public string? Ip { get; set; } }
public class WolRequestDto { public string? Mac { get; set; } }
}

View File

@@ -0,0 +1,8 @@
namespace Inventario.API.DTOs
{
public class AsociacionUsuarioDto
{
// Usamos 'required' para asegurar que este campo nunca sea nulo al recibirlo.
public required string Username { get; set; }
}
}

View File

@@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+10f2f2ba67eac900e8a7b63ced2f3689c309fa6f")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+80210e5d4c8c41b94acb737e8a8d9935a2ef21b6")]
[assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")]
[assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")] [assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]