BCards/src/BCards.Web/Controllers/ModerationController.cs
Ricardo Carneiro 3d2ce1f8cf
All checks were successful
BCards Deployment Pipeline / Run Tests (push) Successful in 4s
BCards Deployment Pipeline / PR Validation (push) Has been skipped
BCards Deployment Pipeline / Build and Push Image (push) Successful in 15m22s
BCards Deployment Pipeline / Deploy to Production (ARM - OCI) (push) Successful in 1m54s
BCards Deployment Pipeline / Deploy to Test (x86 - Local) (push) Has been skipped
BCards Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Deployment Pipeline / Deployment Summary (push) Successful in 0s
fix: Increase session timeout to 7 days and set SameSite=None for Cloudflare compatibility
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-18 12:32:42 -03:00

232 lines
8.3 KiB
C#

using Microsoft.AspNetCore.Mvc;
using BCards.Web.Services;
using BCards.Web.Models;
using BCards.Web.ViewModels;
using BCards.Web.Repositories;
using System.Security.Claims;
using BCards.Web.Attributes;
namespace BCards.Web.Controllers;
[ModeratorAuthorize]
[Route("Moderation")]
public class ModerationController : Controller
{
private readonly IModerationService _moderationService;
private readonly IEmailService _emailService;
private readonly IUserRepository _userRepository;
private readonly ILogger<ModerationController> _logger;
public ModerationController(
IModerationService moderationService,
IEmailService emailService,
IUserRepository userRepository,
ILogger<ModerationController> logger)
{
_moderationService = moderationService;
_emailService = emailService;
_userRepository = userRepository;
_logger = logger;
}
[HttpGet("Dashboard")]
public async Task<IActionResult> Dashboard(int page = 1, int size = 20, string? filter = null)
{
var skip = (page - 1) * size;
var pendingPages = await _moderationService.GetPendingModerationAsync(skip, size, filter);
var stats = await _moderationService.GetModerationStatsAsync();
var viewModel = new ModerationDashboardViewModel
{
PendingPages = pendingPages.Select(p => new PendingPageViewModel
{
Id = p.Id,
DisplayName = p.DisplayName,
Category = p.Category,
Slug = p.Slug,
CreatedAt = p.CreatedAt,
ModerationAttempts = p.ModerationAttempts,
PlanType = p.PlanLimitations.PlanType.ToString(),
IsSpecialModeration = p.PlanLimitations.SpecialModeration ?? false,
PreviewUrl = !string.IsNullOrEmpty(p.PreviewToken)
? $"/page/{p.Category}/{p.Slug}?preview={p.PreviewToken}"
: null
}).ToList(),
Stats = stats,
CurrentPage = page,
PageSize = size,
HasNextPage = pendingPages.Count == size,
CurrentFilter = filter ?? "all"
};
return View(viewModel);
}
[HttpGet("Review/{id}")]
public async Task<IActionResult> Review(string id)
{
var page = await _moderationService.GetPageForModerationAsync(id);
if (page == null)
{
TempData["Error"] = "Página não encontrada ou não está pendente de moderação.";
return RedirectToAction("Dashboard");
}
var user = await _userRepository.GetByIdAsync(page.UserId);
if (user == null)
{
TempData["Error"] = "Usuário não encontrado.";
return RedirectToAction("Dashboard");
}
var viewModel = new ModerationReviewViewModel
{
Page = page,
User = user,
PreviewUrl = !string.IsNullOrEmpty(page.PreviewToken)
? $"/page/{page.Category}/{page.Slug}?preview={page.PreviewToken}"
: null,
ModerationCriteria = GetModerationCriteria()
};
return View(viewModel);
}
[HttpPost("Approve/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Approve(string id, string notes)
{
try
{
var page = await _moderationService.GetPageForModerationAsync(id);
if (page == null)
{
TempData["Error"] = "Página não encontrada.";
return RedirectToAction("Dashboard");
}
var user = await _userRepository.GetByIdAsync(page.UserId);
var moderatorId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "system";
await _moderationService.ApprovePageAsync(id, moderatorId, notes);
if (user != null)
{
await _emailService.SendModerationStatusAsync(
user.Email,
user.Name,
page.DisplayName,
PageStatus.Active.ToString());
}
TempData["Success"] = $"Página '{page.DisplayName}' aprovada com sucesso!";
return RedirectToAction("Dashboard");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error approving page {PageId}", id);
TempData["Error"] = "Erro ao aprovar página.";
return RedirectToAction("Review", new { id });
}
}
[HttpPost("Reject/{id}")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Reject(string id, string reason, List<string> issues)
{
try
{
var page = await _moderationService.GetPageForModerationAsync(id);
if (page == null)
{
TempData["Error"] = "Página não encontrada.";
return RedirectToAction("Dashboard");
}
var user = await _userRepository.GetByIdAsync(page.UserId);
var moderatorId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "system";
await _moderationService.RejectPageAsync(id, moderatorId, reason, issues);
if (user != null)
{
await _emailService.SendModerationStatusAsync(
user.Email,
user.Name,
page.DisplayName,
"rejected",
reason);
}
TempData["Success"] = $"Página '{page.DisplayName}' rejeitada.";
return RedirectToAction("Dashboard");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error rejecting page {PageId}", id);
TempData["Error"] = "Erro ao rejeitar página.";
return RedirectToAction("Review", new { id });
}
}
[HttpGet("History")]
public async Task<IActionResult> History(int page = 1, int size = 20)
{
var skip = (page - 1) * size;
var historyPages = await _moderationService.GetModerationHistoryAsync(skip, size);
var viewModel = new ModerationHistoryViewModel
{
Pages = historyPages.Select(p => new ModerationPageViewModel
{
Id = p.Id,
DisplayName = p.DisplayName,
Category = p.Category,
Slug = p.Slug,
CreatedAt = p.CreatedAt,
Status = p.Status.ToString(),
ModerationAttempts = p.ModerationAttempts,
PlanType = p.PlanLimitations.PlanType.ToString(),
ApprovedAt = p.ApprovedAt,
LastModerationEntry = p.ModerationHistory.LastOrDefault()
}).ToList(),
CurrentPage = page,
PageSize = size,
HasNextPage = historyPages.Count == size
};
return View(viewModel);
}
private List<ModerationCriterion> GetModerationCriteria()
{
return new List<ModerationCriterion>
{
new() { Category = "Conteúdo Proibido", Items = new List<string>
{
"Pornografia e conteúdo sexual explícito",
"Drogas ilegais e substâncias controladas",
"Armas e explosivos",
"Atividades ilegais (fraudes, pirataria)",
"Apostas e jogos de azar",
"Criptomoedas e esquemas de pirâmide",
"Conteúdo que promove violência ou ódio",
"Spam e links suspeitos/maliciosos"
}},
new() { Category = "Conteúdo Suspeito", Items = new List<string>
{
"Excesso de anúncios (>30% dos links)",
"Sites com pop-ups excessivos",
"Links encurtados suspeitos",
"Conteúdo que imita marcas conhecidas",
"Produtos \"milagrosos\""
}},
new() { Category = "Verificações Técnicas", Items = new List<string>
{
"Links funcionais (não quebrados)",
"Sites com SSL válido",
"Não redirecionamentos maliciosos"
}}
};
}
}