refactor(db): migrate career profile aggregate
Transfer the canonical career profile, revision history, and six relational child collections to provider-aware migration ownership. Preserve legacy JSON, ordered child data, indexes, and cascade semantics.
This commit is contained in:
@@ -11,9 +11,9 @@ namespace JobTrackerApi.Migrations
|
||||
// longtext OwnerUserId and then indexed (OwnerUserId, CareerProfileId, SortOrder) over it,
|
||||
// which exceeds MySQL's 3072-byte key limit — a clean MariaDB install died here.
|
||||
//
|
||||
// The six CareerProfile child tables are provisioned by the idempotent reconciler in
|
||||
// StartupInitializationExtensions, which carries correct DDL per provider. Existing databases
|
||||
// already have this migration recorded as applied, so emptying it changes nothing for them.
|
||||
// The six CareerProfile child tables were initially provisioned by the reconciler. Explicit
|
||||
// ownership moved to 20260830133000_AdoptCareerProfileSchema; this historical migration stays
|
||||
// recorded so existing chains remain compatible.
|
||||
// docs/infrastructure/database-ownership.md.
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace JobTrackerApi.Migrations
|
||||
/// <inheritdoc />
|
||||
public partial class SyncCareerChildKeyLengths : Migration
|
||||
{
|
||||
// Historical snapshot-only migration. Provider-safe ownership moved to
|
||||
// 20260830133000_AdoptCareerProfileSchema.
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using JobTrackerApi.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace JobTrackerApi.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Moves the durable Career Profile aggregate into migration ownership without replacing the
|
||||
/// canonical profile, append-only history, or ordered relational children.
|
||||
/// </summary>
|
||||
[DbContext(typeof(JobTrackerContext))]
|
||||
[Migration("20260830133000_AdoptCareerProfileSchema")]
|
||||
public sealed class AdoptCareerProfileSchema : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (ActiveProvider.Contains("MySql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
migrationBuilder.Sql(MySqlDdl);
|
||||
return;
|
||||
}
|
||||
|
||||
migrationBuilder.Sql(SqliteDdl);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Preserve user career history that may predate explicit migration ownership.
|
||||
}
|
||||
|
||||
private const string SqliteDdl = """
|
||||
CREATE TABLE IF NOT EXISTS "CareerProfiles" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerProfiles" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"ProfileJson" TEXT NOT NULL,
|
||||
"LongTailJson" TEXT NOT NULL DEFAULT '',
|
||||
"Version" INTEGER NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
"UpdatedAtUtc" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerProfileVersions" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerProfileVersions" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"CareerProfileId" INTEGER NOT NULL,
|
||||
"Version" INTEGER NOT NULL,
|
||||
"ProfileJson" TEXT NOT NULL,
|
||||
"Source" TEXT NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CareerProfileVersions_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerExperiences" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerExperiences" PRIMARY KEY AUTOINCREMENT,
|
||||
"Title" TEXT NULL, "Company" TEXT NULL, "Location" TEXT NULL,
|
||||
"Start" TEXT NULL, "End" TEXT NULL, "StartDate" TEXT NULL, "EndDate" TEXT NULL,
|
||||
"IsCurrent" INTEGER NOT NULL DEFAULT 0,
|
||||
"BulletsJson" TEXT NOT NULL DEFAULT '[]', "SkillsJson" TEXT NOT NULL DEFAULT '[]',
|
||||
"CareerProfileId" INTEGER NOT NULL, "OwnerUserId" TEXT NOT NULL,
|
||||
"ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerExperiences_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerEducations" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerEducations" PRIMARY KEY AUTOINCREMENT,
|
||||
"Qualification" TEXT NULL, "QualificationLevel" TEXT NULL, "Institution" TEXT NULL,
|
||||
"Location" TEXT NULL, "Start" TEXT NULL, "End" TEXT NULL,
|
||||
"StartDate" TEXT NULL, "EndDate" TEXT NULL, "DetailsJson" TEXT NOT NULL DEFAULT '[]',
|
||||
"CareerProfileId" INTEGER NOT NULL, "OwnerUserId" TEXT NOT NULL,
|
||||
"ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerEducations_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerSkills" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerSkills" PRIMARY KEY AUTOINCREMENT,
|
||||
"Name" TEXT NULL, "Category" TEXT NULL, "Proficiency" TEXT NULL,
|
||||
"CareerProfileId" INTEGER NOT NULL, "OwnerUserId" TEXT NOT NULL,
|
||||
"ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerSkills_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerProjects" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerProjects" PRIMARY KEY AUTOINCREMENT,
|
||||
"Name" TEXT NULL, "Role" TEXT NULL, "Location" TEXT NULL,
|
||||
"Start" TEXT NULL, "End" TEXT NULL, "StartDate" TEXT NULL, "EndDate" TEXT NULL,
|
||||
"BulletsJson" TEXT NOT NULL DEFAULT '[]', "SkillsJson" TEXT NOT NULL DEFAULT '[]',
|
||||
"LinksJson" TEXT NOT NULL DEFAULT '[]', "CareerProfileId" INTEGER NOT NULL,
|
||||
"OwnerUserId" TEXT NOT NULL, "ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerProjects_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerCertifications" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerCertifications" PRIMARY KEY AUTOINCREMENT,
|
||||
"Name" TEXT NULL, "Issuer" TEXT NULL, "Location" TEXT NULL,
|
||||
"Date" TEXT NULL, "DateNormalized" TEXT NULL, "DetailsJson" TEXT NOT NULL DEFAULT '[]',
|
||||
"CareerProfileId" INTEGER NOT NULL, "OwnerUserId" TEXT NOT NULL,
|
||||
"ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerCertifications_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "CareerLanguages" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CareerLanguages" PRIMARY KEY AUTOINCREMENT,
|
||||
"Name" TEXT NULL, "Level" TEXT NULL, "Notes" TEXT NULL,
|
||||
"CareerProfileId" INTEGER NOT NULL, "OwnerUserId" TEXT NOT NULL,
|
||||
"ItemKey" TEXT NOT NULL, "SortOrder" INTEGER NOT NULL,
|
||||
CONSTRAINT "FK_CareerLanguages_CareerProfiles_CareerProfileId"
|
||||
FOREIGN KEY ("CareerProfileId") REFERENCES "CareerProfiles" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "IX_CareerProfiles_OwnerUserId"
|
||||
ON "CareerProfiles" ("OwnerUserId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerProfileVersions_OwnerUserId_CareerProfileId_Version"
|
||||
ON "CareerProfileVersions" ("OwnerUserId", "CareerProfileId", "Version");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerExperiences_CareerProfileId" ON "CareerExperiences" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerExperiences_OwnerUserId_CareerProfileId_SortOrder" ON "CareerExperiences" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerEducations_CareerProfileId" ON "CareerEducations" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerEducations_OwnerUserId_CareerProfileId_SortOrder" ON "CareerEducations" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerSkills_CareerProfileId" ON "CareerSkills" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerSkills_OwnerUserId_CareerProfileId_SortOrder" ON "CareerSkills" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerProjects_CareerProfileId" ON "CareerProjects" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerProjects_OwnerUserId_CareerProfileId_SortOrder" ON "CareerProjects" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerCertifications_CareerProfileId" ON "CareerCertifications" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerCertifications_OwnerUserId_CareerProfileId_SortOrder" ON "CareerCertifications" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerLanguages_CareerProfileId" ON "CareerLanguages" ("CareerProfileId");
|
||||
CREATE INDEX IF NOT EXISTS "IX_CareerLanguages_OwnerUserId_CareerProfileId_SortOrder" ON "CareerLanguages" ("OwnerUserId", "CareerProfileId", "SortOrder");
|
||||
""";
|
||||
|
||||
private const string MySqlDdl = """
|
||||
CREATE TABLE IF NOT EXISTS `CareerProfiles` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `OwnerUserId` varchar(255) NOT NULL,
|
||||
`ProfileJson` longtext NOT NULL, `LongTailJson` longtext NOT NULL,
|
||||
`Version` int NOT NULL, `CreatedAtUtc` datetime(6) NOT NULL,
|
||||
`UpdatedAtUtc` datetime(6) NOT NULL, PRIMARY KEY (`Id`)
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerProfileVersions` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `OwnerUserId` varchar(255) NOT NULL,
|
||||
`CareerProfileId` int NOT NULL, `Version` int NOT NULL, `ProfileJson` longtext NOT NULL,
|
||||
`Source` varchar(100) NOT NULL, `CreatedAtUtc` datetime(6) NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerProfileVersions_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerExperiences` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Title` longtext NULL, `Company` longtext NULL,
|
||||
`Location` longtext NULL, `Start` longtext NULL, `End` longtext NULL,
|
||||
`StartDate` longtext NULL, `EndDate` longtext NULL, `IsCurrent` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`BulletsJson` longtext NOT NULL, `SkillsJson` longtext NOT NULL,
|
||||
`CareerProfileId` int NOT NULL, `OwnerUserId` varchar(255) NOT NULL,
|
||||
`ItemKey` varchar(255) NOT NULL, `SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerExperiences_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerEducations` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Qualification` longtext NULL,
|
||||
`QualificationLevel` longtext NULL, `Institution` longtext NULL, `Location` longtext NULL,
|
||||
`Start` longtext NULL, `End` longtext NULL, `StartDate` longtext NULL, `EndDate` longtext NULL,
|
||||
`DetailsJson` longtext NOT NULL, `CareerProfileId` int NOT NULL,
|
||||
`OwnerUserId` varchar(255) NOT NULL, `ItemKey` varchar(255) NOT NULL,
|
||||
`SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerEducations_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerSkills` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Name` longtext NULL, `Category` longtext NULL,
|
||||
`Proficiency` longtext NULL, `CareerProfileId` int NOT NULL,
|
||||
`OwnerUserId` varchar(255) NOT NULL, `ItemKey` varchar(255) NOT NULL,
|
||||
`SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerSkills_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerProjects` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Name` longtext NULL, `Role` longtext NULL,
|
||||
`Location` longtext NULL, `Start` longtext NULL, `End` longtext NULL,
|
||||
`StartDate` longtext NULL, `EndDate` longtext NULL, `BulletsJson` longtext NOT NULL,
|
||||
`SkillsJson` longtext NOT NULL, `LinksJson` longtext NOT NULL,
|
||||
`CareerProfileId` int NOT NULL, `OwnerUserId` varchar(255) NOT NULL,
|
||||
`ItemKey` varchar(255) NOT NULL, `SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerProjects_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerCertifications` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Name` longtext NULL, `Issuer` longtext NULL,
|
||||
`Location` longtext NULL, `Date` longtext NULL, `DateNormalized` longtext NULL,
|
||||
`DetailsJson` longtext NOT NULL, `CareerProfileId` int NOT NULL,
|
||||
`OwnerUserId` varchar(255) NOT NULL, `ItemKey` varchar(255) NOT NULL,
|
||||
`SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerCertifications_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS `CareerLanguages` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT, `Name` longtext NULL, `Level` longtext NULL,
|
||||
`Notes` longtext NULL, `CareerProfileId` int NOT NULL,
|
||||
`OwnerUserId` varchar(255) NOT NULL, `ItemKey` varchar(255) NOT NULL,
|
||||
`SortOrder` int NOT NULL, PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CareerLanguages_CareerProfiles_CareerProfileId`
|
||||
FOREIGN KEY (`CareerProfileId`) REFERENCES `CareerProfiles` (`Id`) ON DELETE CASCADE
|
||||
) CHARACTER SET=utf8mb4;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS `IX_CareerProfiles_OwnerUserId` ON `CareerProfiles` (`OwnerUserId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerProfileVersions_OwnerUserId_CareerProfileId_Version` ON `CareerProfileVersions` (`OwnerUserId`, `CareerProfileId`, `Version`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerExperiences_CareerProfileId` ON `CareerExperiences` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerExperiences_OwnerUserId_CareerProfileId_SortOrder` ON `CareerExperiences` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerEducations_CareerProfileId` ON `CareerEducations` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerEducations_OwnerUserId_CareerProfileId_SortOrder` ON `CareerEducations` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerSkills_CareerProfileId` ON `CareerSkills` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerSkills_OwnerUserId_CareerProfileId_SortOrder` ON `CareerSkills` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerProjects_CareerProfileId` ON `CareerProjects` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerProjects_OwnerUserId_CareerProfileId_SortOrder` ON `CareerProjects` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerCertifications_CareerProfileId` ON `CareerCertifications` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerCertifications_OwnerUserId_CareerProfileId_SortOrder` ON `CareerCertifications` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerLanguages_CareerProfileId` ON `CareerLanguages` (`CareerProfileId`);
|
||||
CREATE INDEX IF NOT EXISTS `IX_CareerLanguages_OwnerUserId_CareerProfileId_SortOrder` ON `CareerLanguages` (`OwnerUserId`, `CareerProfileId`, `SortOrder`);
|
||||
""";
|
||||
}
|
||||
Reference in New Issue
Block a user