using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using PowderCoating.Core.Interfaces; using PowderCoating.Shared.Constants; namespace PowderCoating.Web.Controllers; /// /// Handles customer self-service email opt-out via tokenized links. /// No authentication required — the token IS the proof of identity. /// [AllowAnonymous] [EnableRateLimiting(AppConstants.RateLimitPolicies.Public)] public class UnsubscribeController : Controller { private readonly IUnitOfWork _unitOfWork; private readonly ILogger _logger; public UnsubscribeController(IUnitOfWork unitOfWork, ILogger logger) { _unitOfWork = unitOfWork; _logger = logger; } /// /// GET /Unsubscribe/Email/{token} /// Called when a customer clicks the unsubscribe link in an email. /// Sets NotifyByEmail = false and shows a confirmation page. /// [HttpGet] [Route("Unsubscribe/Email/{token}")] public async Task Email(string token) { if (string.IsNullOrWhiteSpace(token)) return View("Invalid"); try { // ignoreQueryFilters=true so we can find the customer by token // regardless of company context (the user clicking is not authenticated) var customer = await _unitOfWork.Customers.FirstOrDefaultAsync( c => c.UnsubscribeToken == token && !c.IsDeleted, ignoreQueryFilters: true); if (customer == null) { _logger.LogWarning("Unsubscribe attempt with unknown token: {Token}", token); return View("Invalid"); } if (!customer.NotifyByEmail) { ViewBag.CustomerName = customer.CompanyName ?? $"{customer.ContactFirstName} {customer.ContactLastName}".Trim(); ViewBag.AlreadyUnsubscribed = true; return View("EmailConfirm"); } customer.NotifyByEmail = false; customer.UpdatedAt = DateTime.UtcNow; await _unitOfWork.CompleteAsync(); _logger.LogInformation("Customer {CustomerId} unsubscribed from email notifications via link", customer.Id); ViewBag.CustomerName = customer.CompanyName ?? $"{customer.ContactFirstName} {customer.ContactLastName}".Trim(); ViewBag.AlreadyUnsubscribed = false; return View("EmailConfirm"); } catch (Exception ex) { _logger.LogError(ex, "Error processing unsubscribe for token {Token}", token); return View("Invalid"); } } /// /// GET /Unsubscribe/BroadcastEmail/{token} /// Opts a company's primary contact out of platform broadcast/marketing emails. /// [HttpGet] [Route("Unsubscribe/BroadcastEmail/{token}")] public async Task BroadcastEmail(string token) { if (string.IsNullOrWhiteSpace(token)) return View("Invalid"); try { var company = await _unitOfWork.Companies.FirstOrDefaultAsync( c => c.MarketingUnsubscribeToken == token && !c.IsDeleted, ignoreQueryFilters: true); if (company == null) { _logger.LogWarning("Broadcast unsubscribe attempt with unknown token: {Token}", token); return View("Invalid"); } ViewBag.CompanyName = company.CompanyName; ViewBag.AlreadyUnsubscribed = company.MarketingEmailOptOut; if (!company.MarketingEmailOptOut) { company.MarketingEmailOptOut = true; company.UpdatedAt = DateTime.UtcNow; await _unitOfWork.CompleteAsync(); _logger.LogInformation("Company {CompanyId} opted out of broadcast emails via link", company.Id); } return View("BroadcastEmailConfirm"); } catch (Exception ex) { _logger.LogError(ex, "Error processing broadcast unsubscribe for token {Token}", token); return View("Invalid"); } } }