using BCards.Web.Models; using BCards.Web.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; namespace BCards.Web.Controllers; [Authorize] [ApiController] [Route("api/[controller]")] public class ProductController : ControllerBase { private readonly IOpenGraphService _openGraphService; private readonly ILogger _logger; public ProductController( IOpenGraphService openGraphService, ILogger logger) { _openGraphService = openGraphService; _logger = logger; } [HttpPost("extract")] public async Task ExtractProduct([FromBody] ExtractProductRequest request) { try { if (string.IsNullOrWhiteSpace(request.Url)) { return BadRequest(new { success = false, message = "URL é obrigatória." }); } if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri)) { return BadRequest(new { success = false, message = "URL inválida." }); } var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userId)) { return Unauthorized(new { success = false, message = "Usuário não autenticado." }); } // Verificar rate limiting antes de tentar extrair var isRateLimited = await _openGraphService.IsRateLimitedAsync(userId); if (isRateLimited) { return this.TooManyRequests(new { success = false, message = "Aguarde 1 minuto antes de extrair dados de outro produto." }); } var ogData = await _openGraphService.ExtractDataAsync(request.Url, userId); if (!ogData.IsValid) { return BadRequest(new { success = false, message = string.IsNullOrEmpty(ogData.ErrorMessage) ? "Não foi possível extrair dados desta página." : ogData.ErrorMessage }); } return Ok(new { success = true, title = ogData.Title, description = ogData.Description, image = ogData.Image, price = ogData.Price, currency = ogData.Currency }); } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "Operação inválida na extração de produto para usuário {UserId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value); return BadRequest(new { success = false, message = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, "Erro interno na extração de produto para usuário {UserId}", User.FindFirst(ClaimTypes.NameIdentifier)?.Value); return StatusCode(500, new { success = false, message = "Erro interno do servidor. Tente novamente em alguns instantes." }); } } [HttpGet("cache/{urlHash}")] public Task GetCachedData(string urlHash) { try { // Por segurança, vamos reconstruir a URL a partir do hash (se necessário) // Por agora, apenas retornamos erro se não encontrado return Task.FromResult(NotFound(new { success = false, message = "Cache não encontrado." })); } catch (Exception ex) { _logger.LogError(ex, "Erro ao buscar cache para hash {UrlHash}", urlHash); return Task.FromResult(StatusCode(500, new { success = false, message = "Erro interno do servidor." })); } } } public class ExtractProductRequest { public string Url { get; set; } = string.Empty; } // Custom result for 429 Too Many Requests public class TooManyRequestsResult : ObjectResult { public TooManyRequestsResult(object value) : base(value) { StatusCode = 429; } } public static class ControllerBaseExtensions { public static TooManyRequestsResult TooManyRequests(this ControllerBase controller, object value) { return new TooManyRequestsResult(value); } }