131 lines
4.4 KiB
C#
131 lines
4.4 KiB
C#
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<ProductController> _logger;
|
|
|
|
public ProductController(
|
|
IOpenGraphService openGraphService,
|
|
ILogger<ProductController> logger)
|
|
{
|
|
_openGraphService = openGraphService;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpPost("extract")]
|
|
public async Task<IActionResult> 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<IActionResult> 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<IActionResult>(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<IActionResult>(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);
|
|
}
|
|
} |