using MemberCenter.Api.Contracts; using MemberCenter.Application.Abstractions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenIddict.Abstractions; namespace MemberCenter.Api.Controllers; [ApiController] [Route("newsletter")] public class NewsletterController : ControllerBase { private readonly INewsletterService _newsletterService; public NewsletterController(INewsletterService newsletterService) { _newsletterService = newsletterService; } [HttpPost("subscribe")] public async Task Subscribe([FromBody] SubscribeRequest request) { var result = await _newsletterService.SubscribeAsync(request.ListId, request.Email, request.Preferences); if (result is null) { return NotFound("List not found."); } return Ok(new { id = result.Subscription.Id, list_id = result.Subscription.ListId, email = result.Subscription.Email, status = result.Subscription.Status, created_at = result.Subscription.CreatedAt, confirm_token = result.ConfirmToken }); } [HttpGet("confirm")] public async Task Confirm([FromQuery] string token) { var subscription = await _newsletterService.ConfirmAsync(token); if (subscription is null) { return NotFound("Invalid token."); } return Ok(new { id = subscription.Id, list_id = subscription.ListId, email = subscription.Email, status = subscription.Status, created_at = subscription.CreatedAt }); } [HttpPost("unsubscribe")] public async Task Unsubscribe([FromBody] UnsubscribeRequest request) { var subscription = await _newsletterService.UnsubscribeAsync(request.Token); if (subscription is null) { return NotFound("Invalid token."); } return Ok(new { id = subscription.Id, list_id = subscription.ListId, email = subscription.Email, status = subscription.Status, created_at = subscription.CreatedAt }); } [HttpPost("unsubscribe-token")] public async Task IssueUnsubscribeToken([FromBody] IssueUnsubscribeTokenRequest request) { if (request.ListId == Guid.Empty || string.IsNullOrWhiteSpace(request.Email)) { return BadRequest("Both list_id and email are required."); } var token = await _newsletterService.IssueUnsubscribeTokenAsync(request.ListId, request.Email); if (token is null) { return NotFound("Subscription not found."); } return Ok(new { unsubscribe_token = token }); } [HttpGet("preferences")] public async Task Preferences([FromQuery(Name = "list_id")] Guid? listId, [FromQuery] string? email) { if (!listId.HasValue || listId.Value == Guid.Empty || string.IsNullOrWhiteSpace(email)) { return BadRequest("Both list_id and email are required."); } var subscription = await _newsletterService.GetPreferencesAsync(listId.Value, email); if (subscription is null) { return NotFound("Subscription not found."); } return Ok(new { id = subscription.Id, list_id = subscription.ListId, email = subscription.Email, status = subscription.Status, preferences = subscription.Preferences }); } [HttpPost("preferences")] public async Task UpdatePreferences([FromBody] UpdatePreferencesRequest request) { if (request.ListId == Guid.Empty || string.IsNullOrWhiteSpace(request.Email)) { return BadRequest("Both list_id and email are required."); } var subscription = await _newsletterService.UpdatePreferencesAsync(request.ListId, request.Email, request.Preferences); if (subscription is null) { return NotFound("Subscription not found."); } return Ok(new { id = subscription.Id, list_id = subscription.ListId, email = subscription.Email, status = subscription.Status, preferences = subscription.Preferences }); } [Authorize] [HttpGet("subscriptions")] public async Task ListSubscriptions([FromQuery(Name = "list_id")] Guid listId) { if (!HasScope(User, "newsletter:list.read")) { return Forbid(); } if (listId == Guid.Empty) { return BadRequest("list_id is required."); } var subscriptions = await _newsletterService.ListSubscriptionsAsync(listId); return Ok(subscriptions.Select(s => new { id = s.Id, list_id = s.ListId, email = s.Email, status = s.Status, preferences = s.Preferences, created_at = s.CreatedAt })); } private static bool HasScope(System.Security.Claims.ClaimsPrincipal user, string scope) { var values = user.FindAll(OpenIddictConstants.Claims.Scope) .SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)); return values.Contains(scope, StringComparer.Ordinal); } }