using BCards.Web.Models; using BCards.Web.Services; using BCards.Web.Utils; using BCards.Web.ViewModels; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Security.Claims; using System.Text.Json; namespace BCards.Web.Controllers; [Authorize] [Route("Admin")] public class AdminController : Controller { private readonly IAuthService _authService; private readonly IUserPageService _userPageService; private readonly ICategoryService _categoryService; private readonly IThemeService _themeService; private readonly IModerationService _moderationService; private readonly IEmailService _emailService; private readonly ILivePageService _livePageService; private readonly IImageStorageService _imageStorage; private readonly IPaymentService _paymentService; private readonly ILogger _logger; public AdminController( IAuthService authService, IUserPageService userPageService, ICategoryService categoryService, IThemeService themeService, IModerationService moderationService, IEmailService emailService, ILivePageService livePageService, IImageStorageService imageStorage, IPaymentService paymentService, ILogger logger) { _authService = authService; _userPageService = userPageService; _categoryService = categoryService; _themeService = themeService; _moderationService = moderationService; _emailService = emailService; _livePageService = livePageService; _imageStorage = imageStorage; _paymentService = paymentService; _logger = logger; } [HttpGet] [Route("Dashboard")] public async Task Dashboard() { ViewBag.IsHomePage = false; // Menu normal do dashboard var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); _logger.LogInformation("[DASHBOARD DEBUG] User {UserId} ({Email}) - CurrentPlan: '{CurrentPlan}'", user.Id, user.Email, user.CurrentPlan ?? "NULL"); var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; _logger.LogInformation("[DASHBOARD DEBUG] Parsed PlanType: {PlanType} (from '{CurrentPlan}')", userPlanType, user.CurrentPlan ?? "NULL"); var userPages = await _userPageService.GetUserPagesAsync(user.Id); var listCounts = new Dictionary(); // Atualizar status das baseado nas livepasges foreach (var page in userPages) { if (page.Status == ViewModels.PageStatus.Active) { var livePage = await _livePageService.GetLivePageFromUserPageId(page.Id); if (livePage != null) { listCounts.Add(page.Id, new { TotalViews = livePage.Analytics?.TotalViews ?? 0, TotalClicks = livePage.Analytics?.TotalClicks ?? 0 }); } } else { listCounts.Add(page.Id, new { TotalViews = (long)(page.Analytics?.TotalViews ?? 0), TotalClicks = (long)(page.Analytics?.TotalClicks ?? 0) }); } } var dashboardModel = new DashboardViewModel { CurrentUser = user, UserPages = userPages.Select(p => new UserPageSummary { Id = p.Id, DisplayName = p.DisplayName, Slug = p.Slug, Category = p.Category, Status = p.Status, TotalClicks = listCounts[p.Id].TotalClicks ?? 0, TotalViews = listCounts[p.Id].TotalViews ?? 0, PreviewToken = p.PreviewToken, CreatedAt = p.CreatedAt, LastModerationStatus = p.ModerationHistory == null || p.ModerationHistory.Count == 0 || p.ModerationHistory.Last().Status == "rejected" ? null : Enum.Parse(p.ModerationHistory.Last().Status, true), Motive = p.ModerationHistory == null || p.ModerationHistory.Count == 0 || p.ModerationHistory.Last().Status != "rejected" ? "" : p.ModerationHistory.Last().Reason }).ToList(), CurrentPlan = new PlanInfo { Type = userPlanType, Name = userPlanType.GetDisplayName(), MaxPages = userPlanType.GetMaxPages(), MaxLinksPerPage = userPlanType.GetMaxLinksPerPage(), DurationDays = userPlanType.GetTrialDays(), Price = userPlanType.GetPrice(), AllowsAnalytics = userPlanType.AllowsAnalytics(), AllowsCustomThemes = userPlanType.AllowsCustomThemes() }, CanCreateNewPage = userPages.Count < userPlanType.GetMaxPages(), DaysRemaining = userPlanType == PlanType.Trial ? CalculateTrialDaysRemaining(user) : 0 }; return View(dashboardModel); } private int CalculateTrialDaysRemaining(User user) { // This would be calculated based on subscription data // For now, return a default value return 7; } [HttpGet] [Route("ManagePage")] public async Task ManagePage(string id = null) { ViewBag.IsHomePage = false; var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); if (string.IsNullOrEmpty(id) || id == "new") { // Check if user can create new page var existingPages = await _userPageService.GetUserPagesAsync(user.Id); var maxPages = userPlanType.GetMaxPages(); if (existingPages.Count >= maxPages) { TempData["Error"] = $"Você já atingiu o limite de {maxPages} página(s) do seu plano atual. Faça upgrade para criar mais páginas."; return RedirectToAction("Dashboard"); } // CRIAR NOVA PÁGINA var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString()); var model = new ManagePageViewModel { IsNewPage = true, AvailableCategories = categories, AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(), AllowProductLinks = planLimitations.AllowProductLinks }; return View(model); } else { // EDITAR PÁGINA EXISTENTE var page = await _userPageService.GetPageByIdAsync(id); if (page == null || page.UserId != user.Id) return NotFound(); var model = await MapToManageViewModel(page, categories, themes, userPlanType); return View(model); } } [HttpPost] [Route("ManagePage")] [RequestSizeLimit(5 * 1024 * 1024)] // Allow 5MB uploads [RequestFormLimits(MultipartBodyLengthLimit = 5 * 1024 * 1024)] public async Task ManagePage(ManagePageViewModel model) { ViewBag.IsHomePage = false; var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); // Limpar campos de redes sociais que são apenas espaços (tratados como vazios) CleanSocialMediaFields(model); _logger.LogInformation($"ManagePage POST: IsNewPage={model.IsNewPage}, DisplayName={model.DisplayName}, Category={model.Category}, Links={model.Links?.Count ?? 0}"); ModelState.Remove(x => x.InstagramUrl); ModelState.Remove(x => x.FacebookUrl); ModelState.Remove(x => x.TwitterUrl); ModelState.Remove(x => x.WhatsAppNumber); _logger.LogInformation($"ManagePage POST: IsNewPage={model.IsNewPage}, DisplayName={model.DisplayName}, Category={model.Category}, Links={model.Links?.Count ?? 0}"); //Logar modelstate em information _logger.LogInformation($"ModelState: {JsonSerializer.Serialize(ModelState)}"); // Processar upload de imagem se fornecida if (model.ProfileImageFile != null && model.ProfileImageFile.Length > 0) { try { using var memoryStream = new MemoryStream(); await model.ProfileImageFile.CopyToAsync(memoryStream); var imageBytes = memoryStream.ToArray(); var imageId = await _imageStorage.SaveImageAsync( imageBytes, model.ProfileImageFile.FileName, model.ProfileImageFile.ContentType ); model.ProfileImageId = imageId; _logger.LogInformation("Profile image uploaded successfully: {ImageId}", imageId); } catch (Exception ex) { _logger.LogError(ex, "Error uploading profile image"); ModelState.AddModelError("ProfileImageFile", "Erro ao fazer upload da imagem. Tente novamente."); TempData["ImageError"] = "Erro ao processar a imagem. Verifique o formato e tamanho."; // Preservar dados do form e repopular dropdowns var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString()); model.AvailableCategories = await _categoryService.GetAllCategoriesAsync(); model.AvailableThemes = await _themeService.GetAvailableThemesAsync(); model.MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(); model.AllowProductLinks = planLimitations.AllowProductLinks; // Preservar ProfileImageId existente se estava editando if (!model.IsNewPage && !string.IsNullOrEmpty(model.Id)) { var existingPage = await _userPageService.GetPageByIdAsync(model.Id); if (existingPage != null) { model.ProfileImageId = existingPage.ProfileImageId; } } return View(model); } } if (!ModelState.IsValid) { _logger.LogWarning("ModelState is invalid:"); foreach (var error in ModelState) { _logger.LogWarning($"Key: {error.Key}, Errors: {string.Join(", ", error.Value.Errors.Select(e => e.ErrorMessage))}"); } // Repopulate dropdowns var slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName); model.Slug = slug; model.AvailableCategories = await _categoryService.GetAllCategoriesAsync(); model.AvailableThemes = await _themeService.GetAvailableThemesAsync(); return View(model); } if (model.IsNewPage) { // Generate slug if not provided if (string.IsNullOrEmpty(model.Slug)) { model.Slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName); } // Check if slug is available if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug)) { ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra."); model.AvailableCategories = await _categoryService.GetAllCategoriesAsync(); model.AvailableThemes = await _themeService.GetAvailableThemesAsync(); return View(model); } // Check if user can create the requested number of links var activeLinksCount = model.Links?.Count ?? 0; if (activeLinksCount > model.MaxLinksAllowed) { ModelState.AddModelError("", $"Você excedeu o limite de {model.MaxLinksAllowed} links do seu plano atual."); model.AvailableCategories = await _categoryService.GetAllCategoriesAsync(); model.AvailableThemes = await _themeService.GetAvailableThemesAsync(); return View(model); } try { // Create new page var userPage = await MapToUserPage(model, user.Id); _logger.LogInformation($"Mapped to UserPage: {userPage.DisplayName}, Category: {userPage.Category}, Slug: {userPage.Slug}"); // Set status to Creating for new pages userPage.Status = ViewModels.PageStatus.Creating; await _userPageService.CreatePageAsync(userPage); _logger.LogInformation("Page created successfully!"); // Generate preview token for development var previewToken = await _moderationService.GeneratePreviewTokenAsync(userPage.Id); var previewUrl = $"{Request.Scheme}://{Request.Host}/page/{userPage.Category}/{userPage.Slug}?preview={previewToken}"; userPage.PreviewToken = previewToken; userPage.PreviewTokenExpiry = DateTime.UtcNow.AddHours(4); await _userPageService.UpdatePageAsync(userPage); TempData["Success"] = "Página criada com sucesso! Use o botão 'Enviar para Moderação' quando estiver pronta."; } catch (Exception ex) { _logger.LogError(ex, "Error creating page"); ModelState.AddModelError("", "Erro ao criar página. Tente novamente."); model.AvailableCategories = await _categoryService.GetAllCategoriesAsync(); model.AvailableThemes = await _themeService.GetAvailableThemesAsync(); TempData["Error"] = $"Erro ao criar página. Tente novamente. TechMsg: {ex.Message}"; return View(model); } } else { // Update existing page var existingPage = await _userPageService.GetPageByIdAsync(model.Id); if (existingPage == null || existingPage.UserId != user.Id) return NotFound(); // Check if user can create pages (for users with rejected pages) var canCreatePage = await _moderationService.CanUserCreatePageAsync(user.Id); if (!canCreatePage) { TempData["Error"] = "Você não pode editar páginas devido a muitas rejeições. Entre em contato com o suporte."; return RedirectToAction("Dashboard"); } // IMPORTANTE: Tratar remoção de imagem ou preservar existente se não houver novo upload if (model.ProfileImageFile == null || model.ProfileImageFile.Length == 0) { if (model.ProfileImageId == "REMOVE_IMAGE") { // Usuário quer remover a imagem existente model.ProfileImageId = null; _logger.LogInformation("Profile image removed by user request"); } else { // Preservar imagem existente model.ProfileImageId = existingPage.ProfileImageId; } } await UpdateUserPageFromModel(existingPage, model); // Set status to PendingModeration for updates existingPage.Status = ViewModels.PageStatus.Creating; existingPage.ModerationAttempts = existingPage.ModerationAttempts; await _userPageService.UpdatePageAsync(existingPage); // Generate new preview token var previewToken = await _moderationService.GeneratePreviewTokenAsync(existingPage.Id); var previewUrl = $"{Request.Scheme}://{Request.Host}/page/{existingPage.Category}/{existingPage.Slug}?preview={previewToken}"; // Send email to user await _emailService.SendModerationStatusAsync( user.Email, user.Name, existingPage.DisplayName, "pending", null, previewUrl); TempData["Success"] = "Página atualizada! Teste e envie para moderação."; } return RedirectToAction("Dashboard"); } [HttpPost] [Route("CreatePage")] public async Task CreatePage(CreatePageViewModel model) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); // Check if user already has a page var existingPage = await _userPageService.GetUserPageAsync(user.Id); if (existingPage != null) return RedirectToAction("EditPage"); if (!ModelState.IsValid) { var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } // Generate slug if not provided if (string.IsNullOrEmpty(model.Slug)) { model.Slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName); } // Check if slug is available if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug)) { ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra."); var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } // Check if user can create the requested number of links var activeLinksCount = model.Links?.Count ?? 0; if (!await _userPageService.CanCreateLinksAsync(user.Id, activeLinksCount)) { ModelState.AddModelError("", "Você excedeu o limite de links do seu plano atual."); var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } // Convert ViewModel to UserPage var userPage = new UserPage { UserId = user.Id, DisplayName = model.DisplayName, Category = model.Category, BusinessType = model.BusinessType, Bio = model.Bio, Slug = model.Slug, Theme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(), Links = model.Links?.Select(l => new LinkItem { Title = l.Title, Url = l.Url, Description = l.Description, Icon = l.Icon, IsActive = true, Order = model.Links.IndexOf(l) }).ToList() ?? new List() }; // Add social media links var socialLinks = new List(); if (!string.IsNullOrEmpty(model.WhatsAppNumber)) { socialLinks.Add(new LinkItem { Title = "WhatsApp", Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Icon = "fab fa-whatsapp", IsActive = true, Order = userPage.Links.Count + socialLinks.Count }); } if (!string.IsNullOrEmpty(model.FacebookUrl)) { socialLinks.Add(new LinkItem { Title = "Facebook", Url = model.FacebookUrl, Icon = "fab fa-facebook", IsActive = true, Order = userPage.Links.Count + socialLinks.Count }); } if (!string.IsNullOrEmpty(model.TwitterUrl)) { socialLinks.Add(new LinkItem { Title = "X / Twitter", Url = model.TwitterUrl, Icon = "fab fa-x-twitter", IsActive = true, Order = userPage.Links.Count + socialLinks.Count }); } if (!string.IsNullOrEmpty(model.InstagramUrl)) { socialLinks.Add(new LinkItem { Title = "Instagram", Url = model.InstagramUrl, Icon = "fab fa-instagram", IsActive = true, Order = userPage.Links.Count + socialLinks.Count }); } userPage.Links.AddRange(socialLinks); await _userPageService.CreatePageAsync(userPage); TempData["Success"] = "Página criada com sucesso!"; return RedirectToAction("Dashboard"); } [HttpGet] [Route("EditPage")] public async Task EditPage() { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); var userPage = await _userPageService.GetUserPageAsync(user.Id); var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; ViewBag.IsNew = userPage == null; return View(userPage ?? new UserPage { UserId = user.Id }); } [HttpPost] [Route("EditPage")] public async Task EditPage(UserPage model) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); if (!ModelState.IsValid) { var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } // Check if user can create the requested number of links var activeLinksCount = model.Links?.Count(l => l.IsActive) ?? 0; if (!await _userPageService.CanCreateLinksAsync(user.Id, activeLinksCount)) { ModelState.AddModelError("", "Você excedeu o limite de links do seu plano atual."); var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } model.UserId = user.Id; // Check if slug is available if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug, model.Id)) { ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra."); var categories = await _categoryService.GetAllCategoriesAsync(); var themes = await _themeService.GetAvailableThemesAsync(); ViewBag.Categories = categories; ViewBag.Themes = themes; return View(model); } if (string.IsNullOrEmpty(model.Id)) { await _userPageService.CreatePageAsync(model); } else { await _userPageService.UpdatePageAsync(model); } TempData["Success"] = "Página salva com sucesso!"; return RedirectToAction("Dashboard"); } [HttpPost] [Route("CheckSlugAvailability")] public async Task CheckSlugAvailability(string category, string slug, string? excludeId = null) { if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(slug)) return Json(new { available = false, message = "Categoria e slug são obrigatórios." }); var isValid = await _userPageService.ValidateSlugAsync(category, slug, excludeId); return Json(new { available = isValid, message = isValid ? "URL disponível!" : "Esta URL já está em uso." }); } [HttpPost] [Route("GenerateSlug")] public async Task GenerateSlug(string category, string name) { if (string.IsNullOrWhiteSpace(category) || string.IsNullOrWhiteSpace(name)) return Json(new { slug = "", category = "" }); var slug = await _userPageService.GenerateSlugAsync(category, name); var categorySlug = SlugHelper.CreateCategorySlug(category).ToLower(); return Json(new { slug = slug, category = categorySlug }); } [HttpGet] [Route("Analytics")] public async Task Analytics() { ViewBag.IsHomePage = false; var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); var userPage = await _userPageService.GetUserPageAsync(user.Id); if (userPage == null || !userPage.PlanLimitations.AllowAnalytics) return RedirectToAction("Dashboard"); return View(userPage); } [HttpPost] [Route("DeletePage/{id}")] public async Task DeletePage(string id) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return RedirectToAction("Login", "Auth"); var userPage = await _userPageService.GetPageByIdAsync(id); if (userPage == null || userPage.UserId != user.Id) { TempData["Error"] = "Página não encontrada!"; return RedirectToAction("Dashboard"); } await _userPageService.DeletePageAsync(userPage.Id); TempData["Success"] = "Página excluída com sucesso!"; return RedirectToAction("Dashboard"); } private async Task MapToManageViewModel(UserPage page, List categories, List themes, PlanType userPlanType) { return new ManagePageViewModel { Id = page.Id, IsNewPage = false, DisplayName = page.DisplayName, Category = page.Category, BusinessType = page.BusinessType, Bio = page.Bio, Slug = page.Slug, SelectedTheme = page.Theme?.Name ?? "minimalist", ProfileImageId = page.ProfileImageId, Links = page.Links?.Select((l, index) => new ManageLinkViewModel { Id = $"link_{index}", Title = l.Title, Url = l.Url, Description = l.Description, Icon = l.Icon, Order = l.Order, IsActive = l.IsActive, Type = l.Type, ProductTitle = l.ProductTitle, ProductImage = l.ProductImage, ProductPrice = l.ProductPrice, ProductDescription = l.ProductDescription, ProductDataCachedAt = l.ProductDataCachedAt }).ToList() ?? new List(), AvailableCategories = categories, AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(), MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(), AllowProductLinks = (await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString())).AllowProductLinks }; } private async Task MapToUserPage(ManagePageViewModel model, string userId) { var theme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(); var userPage = new UserPage { UserId = userId, DisplayName = model.DisplayName, Category = SlugHelper.ConvertCategory(model.Category.ToLower()), BusinessType = model.BusinessType, Bio = model.Bio, Slug = SlugHelper.CreateSlug(model.Slug.ToLower()), Theme = theme, Status = ViewModels.PageStatus.Active, ProfileImageId = model.ProfileImageId, Links = new List() }; // Add regular links if (model.Links?.Any() == true) { userPage.Links.AddRange(model.Links.Where(l => !string.IsNullOrEmpty(l.Title) && !string.IsNullOrEmpty(l.Url)) .Select((l, index) => new LinkItem { Title = l.Title, Url = l.Url.ToLower(), Description = l.Description, Icon = l.Icon, IsActive = l.IsActive, Order = index, Type = l.Type, ProductTitle = l.ProductTitle, ProductImage = l.ProductImage, ProductPrice = l.ProductPrice, ProductDescription = l.ProductDescription, ProductDataCachedAt = l.ProductDataCachedAt })); } // Add social media links var socialLinks = new List(); var currentOrder = userPage.Links.Count; if (!string.IsNullOrEmpty(model.WhatsAppNumber)) { socialLinks.Add(new LinkItem { Title = "WhatsApp", Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Icon = "fab fa-whatsapp", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.FacebookUrl)) { socialLinks.Add(new LinkItem { Title = "Facebook", Url = model.FacebookUrl, Icon = "fab fa-facebook", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.TwitterUrl)) { socialLinks.Add(new LinkItem { Title = "X / Twitter", Url = model.TwitterUrl, Icon = "fab fa-x-twitter", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.InstagramUrl)) { socialLinks.Add(new LinkItem { Title = "Instagram", Url = model.InstagramUrl, Icon = "fab fa-instagram", IsActive = true, Order = currentOrder++ }); } userPage.Links.AddRange(socialLinks); return userPage; } private async Task UpdateUserPageFromModel(UserPage page, ManagePageViewModel model) { page.DisplayName = model.DisplayName; page.Category = model.Category; page.BusinessType = model.BusinessType; page.Bio = model.Bio; page.Slug = model.Slug; page.ProfileImageId = model.ProfileImageId; // CRUCIAL: Atualizar ProfileImageId // CRUCIAL: Atualizar tema selecionado var selectedTheme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(); page.Theme = selectedTheme; page.UpdatedAt = DateTime.UtcNow; // Update links page.Links = new List(); // Add regular links if (model.Links?.Any() == true) { // Validar links de produto baseado no plano do usuário var user = await _authService.GetCurrentUserAsync(User); var userPlanType = Enum.TryParse(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial; var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString()); var filteredLinks = model.Links.Where(l => !string.IsNullOrEmpty(l.Title) && !string.IsNullOrEmpty(l.Url)); foreach (var link in filteredLinks) { // Verificar se usuário pode criar links de produto if (link.Type == LinkType.Product && !planLimitations.AllowProductLinks) { throw new InvalidOperationException("Links de produto disponíveis apenas no plano Premium + Afiliados"); } } page.Links.AddRange(filteredLinks.Select((l, index) => new LinkItem { Title = l.Title, Url = l.Url, Description = l.Description, Icon = l.Icon, IsActive = l.IsActive, Order = index, Type = l.Type, ProductTitle = l.ProductTitle, ProductImage = l.ProductImage, ProductPrice = l.ProductPrice, ProductDescription = l.ProductDescription, ProductDataCachedAt = l.ProductDataCachedAt })); } // Add social media links (same logic as create) var socialLinks = new List(); var currentOrder = page.Links.Count; if (!string.IsNullOrEmpty(model.WhatsAppNumber)) { socialLinks.Add(new LinkItem { Title = "WhatsApp", Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}", Icon = "fab fa-whatsapp", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.FacebookUrl)) { socialLinks.Add(new LinkItem { Title = "Facebook", Url = model.FacebookUrl, Icon = "fab fa-facebook", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.TwitterUrl)) { socialLinks.Add(new LinkItem { Title = "X / Twitter", Url = model.TwitterUrl, Icon = "fab fa-x-twitter", IsActive = true, Order = currentOrder++ }); } if (!string.IsNullOrEmpty(model.InstagramUrl)) { socialLinks.Add(new LinkItem { Title = "Instagram", Url = model.InstagramUrl, Icon = "fab fa-instagram", IsActive = true, Order = currentOrder++ }); } page.Links.AddRange(socialLinks); } [HttpPost] [Route("SubmitForModeration/{id}")] public async Task SubmitForModeration(string id) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return Json(new { success = false, message = "Usuário não autenticado" }); var pageItem = await _userPageService.GetPageByIdAsync(id); if (pageItem == null || pageItem.UserId != user.Id) return Json(new { success = false, message = "Página não encontrada" }); // Validar status atual if (pageItem.Status != ViewModels.PageStatus.Creating && pageItem.Status != ViewModels.PageStatus.Rejected) return Json(new { success = false, message = "Página não pode ser enviada para moderação neste momento" }); // Validar se tem pelo menos 1 link ativo var activeLinksCount = pageItem.Links?.Count(l => l.IsActive) ?? 0; if (activeLinksCount < 1) return Json(new { success = false, message = "Página deve ter pelo menos 1 link ativo para ser enviada" }); try { // Mudar status para PendingModeration pageItem.Status = ViewModels.PageStatus.PendingModeration; pageItem.ModerationAttempts++; pageItem.UpdatedAt = DateTime.UtcNow; await _userPageService.UpdatePageAsync(pageItem); // Enviar email de notificação ao usuário await _emailService.SendModerationStatusAsync( user.Email, user.Name, pageItem.DisplayName, "pending", null, $"{Request.Scheme}://{Request.Host}/page/{pageItem.Category}/{pageItem.Slug}?preview={pageItem.PreviewToken}"); _logger.LogInformation($"Page {pageItem.Id} submitted for moderation by user {user.Id}"); return Json(new { success = true, message = "Página enviada para moderação com sucesso! Você receberá um email quando for processada.", newStatus = "PendingModeration" }); } catch (Exception ex) { _logger.LogError(ex, $"Error submitting page {id} for moderation"); return Json(new { success = false, message = "Erro interno. Tente novamente." }); } } [HttpPost] [Route("RefreshPreviewToken/{id}")] public async Task RefreshPreviewToken(string id) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return Json(new { success = false, message = "Não autorizado" }); var pageItem = await _userPageService.GetPageByIdAsync(id); if (pageItem == null || pageItem.UserId != user.Id) return Json(new { success = false, message = "Página não encontrada" }); // Só renovar token para páginas "Creating" if (pageItem.Status != ViewModels.PageStatus.Creating) return Json(new { success = false, message = "Token só pode ser renovado para páginas em desenvolvimento" }); try { // Gerar novo token com 4 horas de validade var newToken = await _moderationService.GeneratePreviewTokenAsync(pageItem.Id); var previewUrl = $"{Request.Scheme}://{Request.Host}/page/{pageItem.Category}/{pageItem.Slug}?preview={newToken}"; return Json(new { success = true, previewToken = newToken, previewUrl = previewUrl, expiresAt = DateTime.UtcNow.AddHours(4).ToString("yyyy-MM-dd HH:mm:ss") }); } catch (Exception ex) { _logger.LogError(ex, $"Error refreshing preview token for page {id}"); return Json(new { success = false, message = "Erro ao renovar token" }); } } [HttpPost] [Route("GeneratePreviewToken/{id}")] public async Task GeneratePreviewToken(string id) { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return Json(new { success = false, message = "Usuário não autenticado" }); _logger.LogInformation($"Generating preview token for page {id} by user {user.Id}"); var pageItem = await _userPageService.GetPageByIdAsync(id); if (pageItem == null || pageItem.UserId != user.Id) return Json(new { success = false, message = "Página não encontrada" }); _logger.LogInformation($"Generating preview token for page {id} preview token {pageItem.PreviewToken} by user {user.Id}"); // Verificar se página pode ter preview if (pageItem.Status != ViewModels.PageStatus.Creating && pageItem.Status != ViewModels.PageStatus.PendingModeration && pageItem.Status != ViewModels.PageStatus.Rejected) { return Json(new { success = false, message = "Preview não disponível para este status" }); } try { // Gerar novo token com 4 horas de validade var newToken = await _moderationService.GeneratePreviewTokenAsync(pageItem.Id); _logger.LogInformation($"Generating preview token for page {id} preview token {pageItem.PreviewToken} by user {user.Id}"); return Json(new { success = true, previewToken = newToken, message = "Preview gerado com sucesso!", expiresAt = DateTime.UtcNow.AddHours(4).ToString("yyyy-MM-dd HH:mm:ss") }); } catch (Exception ex) { _logger.LogError(ex, $"Error generating preview token for page {id}"); return Json(new { success = false, message = "Erro interno. Tente novamente." }); } } [HttpPost] [Route("MigrateToLivePages")] public async Task MigrateToLivePages() { var user = await _authService.GetCurrentUserAsync(User); if (user == null) return Json(new { success = false, message = "Usuário não autenticado" }); try { // Buscar todas as páginas ativas do usuário atual var activePages = await _userPageService.GetUserPagesAsync(user.Id); var eligiblePages = activePages.Where(p => p.Status == ViewModels.PageStatus.Active).ToList(); if (!eligiblePages.Any()) { return Json(new { success = false, message = "Nenhuma página ativa encontrada para migração" }); } int successCount = 0; int errorCount = 0; var errors = new List(); foreach (var page in eligiblePages) { try { await _livePageService.SyncFromUserPageAsync(page.Id); successCount++; _logger.LogInformation($"Successfully migrated page {page.Id} ({page.DisplayName}) to LivePages"); } catch (Exception ex) { errorCount++; var errorMsg = $"Erro ao migrar '{page.DisplayName}': {ex.Message}"; errors.Add(errorMsg); _logger.LogError(ex, $"Failed to migrate page {page.Id} to LivePages"); } } var message = $"Migração concluída: {successCount} páginas migradas com sucesso"; if (errorCount > 0) { message += $", {errorCount} erros encontrados"; } return Json(new { success = errorCount == 0, message = message, details = new { totalPages = eligiblePages.Count, successCount = successCount, errorCount = errorCount, errors = errors } }); } catch (Exception ex) { _logger.LogError(ex, "Error during LivePages migration"); return Json(new { success = false, message = $"Erro durante migração: {ex.Message}" }); } } private void CleanSocialMediaFields(ManagePageViewModel model) { // Tratar espaço em branco como campo vazio para redes sociais if (string.IsNullOrWhiteSpace(model.WhatsAppNumber) || model.WhatsAppNumber.Trim().Length <= 1) model.WhatsAppNumber = string.Empty; if (string.IsNullOrWhiteSpace(model.FacebookUrl) || model.FacebookUrl.Trim().Length <= 1) model.FacebookUrl = string.Empty; if (string.IsNullOrWhiteSpace(model.InstagramUrl) || model.InstagramUrl.Trim().Length <= 1) model.InstagramUrl = string.Empty; if (string.IsNullOrWhiteSpace(model.TwitterUrl) || model.TwitterUrl.Trim().Length <= 1) model.TwitterUrl = string.Empty; } // 🔥 OTIMIZAÇÃO: Endpoint para manter a sessão do usuário ativa [HttpPost] [Route("KeepAlive")] public IActionResult KeepAlive() { _logger.LogInformation("KeepAlive endpoint triggered for user {User}", User.Identity?.Name ?? "Anonymous"); return Json(new { status = "session_extended" }); } }