BCards/src/BCards.Web/Controllers/ProductController.cs
Ricardo Carneiro 27ae8b606e feat:
+login ms que permite contas corporativas ou não.
+Links para produtos de afiliados
2025-06-25 19:30:19 -03:00

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);
}
}