feat(email): add Correspondence.Provider discriminator
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:
@@ -159,6 +159,7 @@ namespace JobTrackerApi.Controllers
|
||||
ExternalTo = string.IsNullOrWhiteSpace(request.ExternalTo) ? null : request.ExternalTo.Trim(),
|
||||
ExternalLabelsJson = string.IsNullOrWhiteSpace(request.ExternalLabelsJson) ? null : request.ExternalLabelsJson.Trim(),
|
||||
AttachmentMetadataJson = string.IsNullOrWhiteSpace(request.AttachmentMetadataJson) ? null : request.AttachmentMetadataJson.Trim(),
|
||||
Provider = "manual",
|
||||
Content = request.Content,
|
||||
Date = request.Date ?? DateTime.Now,
|
||||
};
|
||||
|
||||
@@ -977,6 +977,7 @@ public sealed class GmailController : ControllerBase
|
||||
GmailAttachmentId = attachment.ExternalAttachmentId,
|
||||
Inline = attachment.Inline,
|
||||
})),
|
||||
Provider = "gmail",
|
||||
Content = string.IsNullOrWhiteSpace(detail.BodyText) ? detail.Snippet : detail.BodyText,
|
||||
Date = messageDate,
|
||||
};
|
||||
|
||||
@@ -529,6 +529,12 @@ public static class StartupInitializationExtensions
|
||||
EnsureColumn(conn, "Correspondences", "Direction", "ALTER TABLE Correspondences ADD COLUMN Direction TEXT NULL;");
|
||||
EnsureColumn(conn, "Correspondences", "ExternalLabelsJson", "ALTER TABLE Correspondences ADD COLUMN ExternalLabelsJson TEXT NULL;");
|
||||
EnsureColumn(conn, "Correspondences", "AttachmentMetadataJson", "ALTER TABLE Correspondences ADD COLUMN AttachmentMetadataJson TEXT NULL;");
|
||||
EnsureColumn(conn, "Correspondences", "Provider", "ALTER TABLE Correspondences ADD COLUMN Provider TEXT NULL;");
|
||||
// Backfill: historically the only import source was Gmail (rows with an
|
||||
// ExternalThreadId); everything else was hand-entered. Idempotent — only touches
|
||||
// rows the app hasn't tagged yet.
|
||||
Exec(conn, "UPDATE Correspondences SET Provider = 'gmail' WHERE Provider IS NULL AND ExternalThreadId IS NOT NULL;");
|
||||
Exec(conn, "UPDATE Correspondences SET Provider = 'manual' WHERE Provider IS NULL;");
|
||||
EnsureColumn(conn, "Attachments", "Purpose", "ALTER TABLE Attachments ADD COLUMN Purpose TEXT NULL;");
|
||||
EnsureColumn(conn, "Attachments", "UseForAi", "ALTER TABLE Attachments ADD COLUMN UseForAi INTEGER NOT NULL DEFAULT 1;");
|
||||
|
||||
@@ -682,6 +688,17 @@ public static class StartupInitializationExtensions
|
||||
EnsureMySqlColumn(conn, "Correspondences", "Direction", "ALTER TABLE `Correspondences` ADD COLUMN `Direction` varchar(100) NULL;");
|
||||
EnsureMySqlColumn(conn, "Correspondences", "ExternalLabelsJson", "ALTER TABLE `Correspondences` ADD COLUMN `ExternalLabelsJson` longtext NULL;");
|
||||
EnsureMySqlColumn(conn, "Correspondences", "AttachmentMetadataJson", "ALTER TABLE `Correspondences` ADD COLUMN `AttachmentMetadataJson` longtext NULL;");
|
||||
EnsureMySqlColumn(conn, "Correspondences", "Provider", "ALTER TABLE `Correspondences` ADD COLUMN `Provider` varchar(50) NULL;");
|
||||
using (var backfillGmail = conn.CreateCommand())
|
||||
{
|
||||
backfillGmail.CommandText = "UPDATE `Correspondences` SET `Provider` = 'gmail' WHERE `Provider` IS NULL AND `ExternalThreadId` IS NOT NULL;";
|
||||
backfillGmail.ExecuteNonQuery();
|
||||
}
|
||||
using (var backfillManual = conn.CreateCommand())
|
||||
{
|
||||
backfillManual.CommandText = "UPDATE `Correspondences` SET `Provider` = 'manual' WHERE `Provider` IS NULL;";
|
||||
backfillManual.ExecuteNonQuery();
|
||||
}
|
||||
EnsureMySqlColumn(conn, "Attachments", "Purpose", "ALTER TABLE `Attachments` ADD COLUMN `Purpose` varchar(100) NULL;");
|
||||
EnsureMySqlColumn(conn, "Attachments", "UseForAi", "ALTER TABLE `Attachments` ADD COLUMN `UseForAi` tinyint(1) NOT NULL DEFAULT 1;");
|
||||
EnsureMySqlColumn(conn, "AspNetUsers", "ProfileCvText", "ALTER TABLE `AspNetUsers` ADD COLUMN `ProfileCvText` longtext NULL;");
|
||||
|
||||
Reference in New Issue
Block a user