using ChatbotApi.Data.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Caching.Memory; using ChatbotApi.Services; using Microsoft.EntityFrameworkCore; namespace ChatbotApi.Controllers { [ApiController] [Route("api/[controller]")] [Authorize] // Requiere Token JWT válido public class AdminController : ControllerBase { private readonly AppContexto _context; private readonly IMemoryCache _cache; public AdminController(AppContexto context, IMemoryCache cache) { _context = context; _cache = cache; } // --- CONTEXTO ITEMS (Sin cambios mayores de seguridad más allá de Authorize) --- [HttpGet("contexto")] public async Task GetAllContextoItems() { var items = await _context.ContextoItems.OrderBy(i => i.Clave).ToListAsync(); return Ok(items); } [HttpPost("contexto")] public async Task CreateContextoItem([FromBody] ContextoItem item) { // [SEGURIDAD] Validación de entrada if (!ModelState.IsValid) return BadRequest(ModelState); if (await _context.ContextoItems.AnyAsync(i => i.Clave == item.Clave)) { return BadRequest("La clave ya existe."); } item.FechaActualizacion = DateTime.UtcNow; _context.ContextoItems.Add(item); await _context.SaveChangesAsync(); _cache.Remove(CacheKeys.KnowledgeItems); return CreatedAtAction(nameof(GetAllContextoItems), new { id = item.Id }, item); } [HttpPut("contexto/{id}")] public async Task UpdateContextoItem(int id, [FromBody] ContextoItem item) { if (id != item.Id) return BadRequest(); var existingItem = await _context.ContextoItems.FindAsync(id); if (existingItem == null) return NotFound(); existingItem.Valor = item.Valor; existingItem.Descripcion = item.Descripcion; existingItem.FechaActualizacion = DateTime.UtcNow; await _context.SaveChangesAsync(); _cache.Remove(CacheKeys.KnowledgeItems); return NoContent(); } [HttpDelete("contexto/{id}")] public async Task DeleteContextoItem(int id) { var item = await _context.ContextoItems.FindAsync(id); if (item == null) return NotFound(); _context.ContextoItems.Remove(item); await _context.SaveChangesAsync(); _cache.Remove(CacheKeys.KnowledgeItems); return NoContent(); } [HttpGet("logs")] public async Task GetConversationLogs( [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate, [FromQuery] string? search) { var query = _context.ConversacionLogs.AsQueryable(); if (startDate.HasValue) { query = query.Where(l => l.Fecha >= startDate.Value); } if (endDate.HasValue) { // Ajustamos al final del día si es necesario, o asumimos fecha exacta query = query.Where(l => l.Fecha <= endDate.Value); } if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(l => l.UsuarioMensaje.Contains(search) || l.BotRespuesta.Contains(search)); } // Limitamos a 500 para evitar sobrecarga pero permitiendo ver resultados de búsqueda var logs = await query .OrderByDescending(log => log.Fecha) .Take(500) .ToListAsync(); return Ok(logs); } // --- FUENTES DE CONTEXTO (APLICAMOS LA SEGURIDAD SSRF) --- [HttpGet("fuentes")] public async Task GetAllFuentes() { var fuentes = await _context.FuentesDeContexto.OrderBy(f => f.Nombre).ToListAsync(); return Ok(fuentes); } [HttpPost("fuentes")] public async Task CreateFuente([FromBody] FuenteContexto fuente) { if (!ModelState.IsValid) return BadRequest(ModelState); // [SEGURIDAD] Validar que la URL no sea interna/maliciosa ANTES de guardarla if (!await UrlSecurity.IsSafeUrlAsync(fuente.Url)) { return BadRequest("La URL proporcionada no es válida o apunta a una dirección interna restringida."); } _context.FuentesDeContexto.Add(fuente); await _context.SaveChangesAsync(); _cache.Remove(CacheKeys.FuentesDeContexto); return CreatedAtAction(nameof(GetAllFuentes), new { id = fuente.Id }, fuente); } [HttpPut("fuentes/{id}")] public async Task UpdateFuente(int id, [FromBody] FuenteContexto fuente) { if (id != fuente.Id) return BadRequest(); // [SEGURIDAD] Validar también en la actualización if (!await UrlSecurity.IsSafeUrlAsync(fuente.Url)) { return BadRequest("La URL proporcionada no es válida o apunta a una dirección interna restringida."); } _context.Entry(fuente).State = EntityState.Modified; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!_context.FuentesDeContexto.Any(e => e.Id == id)) return NotFound(); else throw; } _cache.Remove(CacheKeys.FuentesDeContexto); return NoContent(); } [HttpDelete("fuentes/{id}")] public async Task DeleteFuente(int id) { var fuente = await _context.FuentesDeContexto.FindAsync(id); if (fuente == null) return NotFound(); _context.FuentesDeContexto.Remove(fuente); await _context.SaveChangesAsync(); _cache.Remove(CacheKeys.FuentesDeContexto); return NoContent(); } } }