fix(db): provision CV builder + AI workspace tables via the MySQL-safe reconciler
CI and Deploy / test (push) Successful in 2m19s
CI and Deploy / deploy (push) Failing after 7s

Deploy failed on prod (MariaDB) while the test job was green: backend startup
threw during Database.Migrate(), so deploy.sh's post-deploy health check exited.

Root cause: AddCvVariants/AddAiInteractions were scaffolded against SQLite, so
they bake SQLite type names into their DDL — DateTimeOffset emits `TEXT`, bool/int
emit `INTEGER`, and the PK gets no AUTO_INCREMENT. Run against MariaDB that yields
a structurally wrong table, and the composite index over a TEXT column then trips
"ERROR 1071: Specified key was too long; max key length is 3072 bytes". SQLite
accepts all of it, which is why local/container verification passed.

Fix, following the pattern already used for CareerProfiles/AiWorkspaceNotes:
- both migrations become no-ops; the three tables are reconciler-owned
- reconciler provisions them idempotently per dialect (MySQL: varchar/int
  AUTO_INCREMENT/datetime(6); SQLite: CREATE TABLE IF NOT EXISTS)
- DropMalformedMySqlTable rebuilds a half-built table left by the failed
  migration, guarded on row count so a table with ANY rows is never dropped
- bound the indexed string columns with HasMaxLength so the model matches

Verified against a real MariaDB 11 container: reproduced error 1071, then
confirmed the corrected DDL yields auto_increment PKs, varchar/datetime columns
and all previously-failing indexes. 306 backend tests green; SQLite container
starts clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-18 20:41:29 +02:00
parent 7a74311a71
commit 1430313a20
4 changed files with 206 additions and 113 deletions
+9
View File
@@ -298,6 +298,12 @@ namespace JobTrackerApi.Data
// owns career data. PublicSlug is globally unique so /cv/{slug} can resolve it anonymously.
modelBuilder.Entity<CvVariant>()
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
// Bound the indexed string columns so Pomelo maps them to varchar, not longtext (MariaDB
// cannot index a TEXT/longtext column without a prefix length). Actual prod DDL is applied by
// the reconciler (StartupInitializationExtensions), MySQL-safe; see that file + DbContext note.
modelBuilder.Entity<CvVariant>().Property(x => x.OwnerUserId).HasMaxLength(255);
modelBuilder.Entity<CvVariant>().Property(x => x.PublicSlug).HasMaxLength(64);
modelBuilder.Entity<CvVariantVersion>().Property(x => x.OwnerUserId).HasMaxLength(255);
modelBuilder.Entity<CvVariant>()
.HasIndex(x => x.PublicSlug)
.IsUnique();
@@ -324,6 +330,9 @@ namespace JobTrackerApi.Data
// docs/architecture/ai-career-assistant.md.
modelBuilder.Entity<AiInteraction>()
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
// varchar (not longtext) for the indexed columns — see the CvVariant note above.
modelBuilder.Entity<AiInteraction>().Property(x => x.OwnerUserId).HasMaxLength(255);
modelBuilder.Entity<AiInteraction>().Property(x => x.Module).HasMaxLength(64);
modelBuilder.Entity<AiInteraction>()
.HasIndex(x => new { x.OwnerUserId, x.JobApplicationId, x.Module, x.CreatedAtUtc });
modelBuilder.Entity<AiInteraction>()