diff --git a/src/BCards.Web/Controllers/AdminController.cs b/src/BCards.Web/Controllers/AdminController.cs index 325b1ac..822aec0 100644 --- a/src/BCards.Web/Controllers/AdminController.cs +++ b/src/BCards.Web/Controllers/AdminController.cs @@ -138,49 +138,59 @@ public class AdminController : Controller [Route("ManagePage")] public async Task ManagePage(string id = null) { - ViewBag.IsHomePage = false; + try + { + ViewBag.IsHomePage = false; - var user = await _authService.GetCurrentUserAsync(User); - if (user == null) - return RedirectToAction("Login", "Auth"); + 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(); + 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"); + 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); } - - // CRIAR NOVA PÁGINA - var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString()); - var model = new ManagePageViewModel + else { - 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(); + // 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); + var model = await MapToManageViewModel(page, categories, themes, userPlanType); + return View(model); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in ManagePage GET"); + TempData["Error"] = "Ocorreu um erro ao carregar a página. Tente novamente."; + throw new Exception("Erro ao salvar o bcard", ex); } } @@ -190,197 +200,210 @@ public class AdminController : Controller [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) + string userId = ""; + try { - try + + ViewBag.IsHomePage = false; + + var user = await _authService.GetCurrentUserAsync(User); + if (user == null) + return RedirectToAction("Login", "Auth"); + + userId = user.Id; + + // 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) { - using var memoryStream = new MemoryStream(); - await model.ProfileImageFile.CopyToAsync(memoryStream); - var imageBytes = memoryStream.ToArray(); + 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 - ); + var imageId = await _imageStorage.SaveImageAsync( + imageBytes, + model.ProfileImageFile.FileName, + model.ProfileImageFile.ContentType + ); - model.ProfileImageId = imageId; - _logger.LogInformation("Profile image uploaded successfully: {ImageId}", imageId); + model.ProfileImageId = imageId; + _logger.LogInformation("Profile image uploaded successfully: {ImageId}", imageId); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Userid: {userId} - 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); + } } - catch (Exception ex) + + if (!ModelState.IsValid) { - _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()); - + _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(); - model.MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(); - model.AllowProductLinks = planLimitations.AllowProductLinks; - - // Preservar ProfileImageId existente se estava editando - if (!model.IsNewPage && !string.IsNullOrEmpty(model.Id)) + return View(model); + } + + if (model.IsNewPage) + { + // Generate slug if not provided + if (string.IsNullOrEmpty(model.Slug)) { - var existingPage = await _userPageService.GetPageByIdAsync(model.Id); - if (existingPage != null) + 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!"); + + // Token será gerado apenas quando usuário clicar "Testar Página" + + TempData["Success"] = "Página criada com sucesso! Use o botão 'Enviar para Moderação' quando estiver pronta."; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Userid: {userId} - 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; } } - - 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))}"); - } + await UpdateUserPageFromModel(existingPage, model); - // 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); - } + // Set status to PendingModeration for updates + existingPage.Status = ViewModels.PageStatus.Creating; + existingPage.ModerationAttempts = existingPage.ModerationAttempts; - 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!"); + await _userPageService.UpdatePageAsync(existingPage); // Token será gerado apenas quando usuário clicar "Testar Página" - - 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); + + // Send email to user + await _emailService.SendModerationStatusAsync( + user.Email, + user.Name, + existingPage.DisplayName, + "pending", + null, + null); // previewUrl não é mais necessário - token será gerado no clique + + TempData["Success"] = "Página atualizada! Teste e envie para moderação."; } + + return RedirectToAction("Dashboard"); } - else + catch (Exception ex) { - // 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); - - // Token será gerado apenas quando usuário clicar "Testar Página" - - // Send email to user - await _emailService.SendModerationStatusAsync( - user.Email, - user.Name, - existingPage.DisplayName, - "pending", - null, - null); // previewUrl não é mais necessário - token será gerado no clique - - TempData["Success"] = "Página atualizada! Teste e envie para moderação."; + _logger.LogError(ex, $"Userid: {userId} - Error in ManagePage GET"); + TempData["Error"] = "Ocorreu um erro ao carregar a página. Tente novamente."; + throw new Exception("Erro ao salvar o bcard", ex); } - - return RedirectToAction("Dashboard"); } [HttpPost]