feat(email): add Correspondence.Provider discriminator
CI and Deploy / test (pull_request) Successful in 2m2s
CI and Deploy / deploy (pull_request) Has been skipped

b4 of the multi-provider email roadmap. The manual/free-text correspondence
entry path already existed (CorrespondenceController.Create) -- this slice
was narrower than the roadmap wording suggests: tag every Correspondence row
with which provider it came from (gmail | manual today; microsoft | imap
once those providers grow an import-into-Correspondence path of their own),
not build a new endpoint.

- Correspondence.Provider (nullable string), reconciled via the existing
  EnsureColumn pattern (SQLite + MySQL).
- Idempotent backfill: rows with an ExternalThreadId (historically only
  ever written by Gmail import) get 'gmail'; everything else gets 'manual'.
- GmailController.ImportSingleMessageAsync now tags Provider = "gmail".
- CorrespondenceController.Create now tags Provider = "manual".
- Both write sites use a fixed literal, not request input -- no injection
  surface introduced. Backfill SQL is static, no interpolation.

148/148 green (147 existing + 1 new CorrespondenceControllerTests; the
GmailController import test gained a Provider assertion in place).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-11 19:03:42 +02:00
parent d308f1d5d4
commit cb2715c323
6 changed files with 59 additions and 0 deletions
+4
View File
@@ -21,6 +21,10 @@ namespace JobTrackerApi.Models
public string? ExternalTo { get; set; }
public string? ExternalLabelsJson { get; set; }
public string? AttachmentMetadataJson { get; set; }
// Provider discriminator: "gmail" | "microsoft" | "imap" | "manual". Set at the write
// site (import controller or the manual-entry endpoint), not inferred from other fields,
// so it stays correct even for hand-entered rows that happen to carry external-looking data.
public string? Provider { get; set; }
public string Content { get; set; } = "";
public DateTime Date { get; set; } = DateTime.Now;