using System.Security.Claims; using JobTrackerApi.Services.EmailProviders; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace JobTrackerApi.Controllers; public sealed record EmailMessageDetailDto( string Id, string ThreadId, string Subject, string From, string To, DateTimeOffset? Date, string Snippet, string BodyText, IReadOnlyList Labels, IReadOnlyList Attachments); [ApiController] [Route("api/email")] [Authorize(AuthenticationSchemes = "local")] public sealed class EmailController(IEmailProviderRegistry providers) : ControllerBase { public sealed record ProviderStatus(string Provider, string DisplayName, bool Connected, string? Address, bool CanRead, bool CanSend); [HttpGet("providers")] public async Task>> GetProviders(CancellationToken cancellationToken) { var ownerUserId = GetOwnerUserId(); if (ownerUserId is null) return Unauthorized(); var statuses = new List(providers.All.Count); foreach (var provider in providers.All) { var connection = await provider.GetConnectionAsync(ownerUserId, cancellationToken); statuses.Add(new ProviderStatus( provider.ProviderKey, GetDisplayName(provider.ProviderKey), connection is not null, connection?.Address, CanRead: connection is not null, CanSend: connection?.CanSend ?? false)); } return Ok(statuses); } [HttpGet("messages")] public async Task>> Search( [FromQuery] string provider, [FromQuery] string? q, [FromQuery] int limit = 25, CancellationToken cancellationToken = default) { var resolved = providers.Get(provider); if (resolved is null) return BadRequest("Unknown email provider."); var ownerUserId = GetOwnerUserId(); if (ownerUserId is null) return Unauthorized(); if (await resolved.GetConnectionAsync(ownerUserId, cancellationToken) is null) return Conflict(new ProblemDetails { Title = "Email provider is not connected." }); return Ok(await resolved.SearchAsync(ownerUserId, q, Math.Clamp(limit, 1, 100), cancellationToken)); } [HttpGet("thread")] public async Task>> GetThread( [FromQuery] string provider, [FromQuery] string threadId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(threadId)) return BadRequest("threadId is required."); var resolved = providers.Get(provider); if (resolved is null) return BadRequest("Unknown email provider."); var ownerUserId = GetOwnerUserId(); if (ownerUserId is null) return Unauthorized(); if (await resolved.GetConnectionAsync(ownerUserId, cancellationToken) is null) return Conflict(new ProblemDetails { Title = "Email provider is not connected." }); return Ok(await resolved.ListThreadMessagesAsync(ownerUserId, threadId.Trim(), cancellationToken)); } [HttpGet("message")] public async Task> GetMessage( [FromQuery] string provider, [FromQuery] string messageId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(messageId)) return BadRequest("messageId is required."); var resolved = providers.Get(provider); if (resolved is null) return BadRequest("Unknown email provider."); var ownerUserId = GetOwnerUserId(); if (ownerUserId is null) return Unauthorized(); if (await resolved.GetConnectionAsync(ownerUserId, cancellationToken) is null) return Conflict(new ProblemDetails { Title = "Email provider is not connected." }); var detail = await resolved.GetMessageAsync(ownerUserId, messageId.Trim(), cancellationToken); return Ok(new EmailMessageDetailDto( detail.Id, detail.ThreadId, detail.Subject, detail.From, detail.To, detail.Date, detail.Snippet, detail.BodyText, detail.Labels, detail.Attachments)); } private string? GetOwnerUserId() => User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub"); private static string GetDisplayName(string provider) => provider.ToLowerInvariant() switch { "gmail" => "Gmail", "microsoft" => "Outlook", "imap" => "IMAP", _ => provider, }; }