feat(email): add MicrosoftGraphProvider (Outlook/365 via Graph OAuth)
b2 of the multi-provider email roadmap. Mirrors the Gmail provider's shape end-to-end so the two stay structurally interchangeable: - MicrosoftGraphConnection model + table (reconciler pattern, SQLite+MySQL, same shape as GmailConnection: encrypted refresh/access token, sync state). - MicrosoftGraphOAuthService: auth-code + offline-access flow against login.microsoftonline.com, encrypted token storage via IDataProtector, message search/thread/detail fetch against Microsoft Graph (conversationId stands in for Gmail's threadId), attachment listing. - MicrosoftGraphProvider implements IEmailProvider — no contract changes; the existing seam was already provider-neutral. - MicrosoftGraphController: connect-url/oauth/callback/status/disconnect, mirrors GmailController's OAuth surface exactly (including the popup postMessage handshake). Job-matching/review endpoints stay Gmail-only for now, per the roadmap — generalising those needs the frontend provider picker work, not this slice. - Registered in DI + IEmailProviderRegistry (multi-registration of IEmailProvider, resolved by ProviderKey). - Config: Microsoft:ClientId/ClientSecret/TenantId/RedirectUri, wired through docker-compose.yml + .env.example alongside the existing Google:Gmail* keys. - Tests: MicrosoftGraphControllerTests (OAuth lifecycle) + MicrosoftGraphProviderTests (DTO mapping onto the neutral contract). 147/147 green (135 existing + 12 new). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
namespace JobTrackerApi.Models;
|
||||
|
||||
public sealed class MicrosoftGraphConnection
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string OwnerUserId { get; set; } = "";
|
||||
public string MailAddress { get; set; } = "";
|
||||
public string EncryptedRefreshToken { get; set; } = "";
|
||||
public string? EncryptedAccessToken { get; set; }
|
||||
public DateTimeOffset? AccessTokenExpiresAt { get; set; }
|
||||
public string Scope { get; set; } = "";
|
||||
public DateTimeOffset ConnectedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset? LastSyncedAt { get; set; }
|
||||
public DateTimeOffset? LastSyncAttemptedAt { get; set; }
|
||||
public DateTimeOffset? LastSyncSucceededAt { get; set; }
|
||||
public string? LastSyncMode { get; set; }
|
||||
public string? LastSyncSource { get; set; }
|
||||
public string? LastSyncStatus { get; set; }
|
||||
public string? LastSyncError { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user