using System.Security.Claims;
using JobTrackerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace JobTrackerApi.Controllers;
///
/// Generic IMAP connection lifecycle for mailboxes with no dedicated OAuth provider. Unlike
/// Gmail/Microsoft, there's no OAuth redirect — the caller submits host/username/password once,
/// verifies them by connecting, then encrypts and stores them.
///
[ApiController]
[Route("api/imap")]
[Authorize]
public sealed class ImapController : ControllerBase
{
private readonly IImapService _imap;
public ImapController(IImapService imap)
{
_imap = imap;
}
public sealed record ImapConnectRequest(string Host, int Port, bool UseSsl, string Username, string Password);
public sealed record ImapConnectionStatusDto(
bool Connected,
string? Host,
int? Port,
bool? UseSsl,
string? Username,
DateTimeOffset? ConnectedAt,
DateTimeOffset? LastSyncedAt,
DateTimeOffset? LastSyncAttemptedAt,
DateTimeOffset? LastSyncSucceededAt,
string? LastSyncMode,
string? LastSyncSource,
string? LastSyncStatus,
string? LastSyncError);
[HttpGet("status")]
public async Task> Status(CancellationToken cancellationToken)
{
var ownerUserId = GetRequiredOwnerUserId();
var connection = await _imap.GetConnectionAsync(ownerUserId, cancellationToken);
return Ok(new ImapConnectionStatusDto(
connection is not null,
connection?.Host,
connection?.Port,
connection?.UseSsl,
connection?.Username,
connection?.ConnectedAt,
connection?.LastSyncedAt,
connection?.LastSyncAttemptedAt,
connection?.LastSyncSucceededAt,
connection?.LastSyncMode,
connection?.LastSyncSource,
connection?.LastSyncStatus,
connection?.LastSyncError));
}
[HttpPost("connect")]
public async Task Connect([FromBody] ImapConnectRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.Host)) return BadRequest("Host is required.");
if (request.Port <= 0 || request.Port > 65535) return BadRequest("Valid port is required.");
if (string.IsNullOrWhiteSpace(request.Username)) return BadRequest("Username is required.");
if (string.IsNullOrWhiteSpace(request.Password)) return BadRequest("Password is required.");
var ownerUserId = GetRequiredOwnerUserId();
try
{
var result = await _imap.ConnectAsync(ownerUserId, request.Host, request.Port, request.UseSsl, request.Username, request.Password, cancellationToken);
return Ok(new { username = result.Username });
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
[HttpDelete("connection")]
public async Task Disconnect(CancellationToken cancellationToken)
{
var ownerUserId = GetRequiredOwnerUserId();
await _imap.DisconnectAsync(ownerUserId, cancellationToken);
return NoContent();
}
private string GetRequiredOwnerUserId()
{
return User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub")
?? throw new InvalidOperationException("Authenticated user id is missing.");
}
}