3a4c8fbc10
Phase 3, schema layer. Relational children of CareerProfile — the editable master career profile. See docs/architecture/career-profile-model.md. - New entities (Models/CareerEntities.cs): CareerExperience, CareerEducation, CareerSkill, CareerProject, CareerCertification, CareerLanguage. Each carries OwnerUserId (tenant filter), a stable ItemKey (carried from the blob so future CV variants can reference items), and SortOrder. List fields persist as JSON string columns via [NotMapped] accessors — plain TEXT, reconciler-friendly. - CareerProfile gains typed child collections + a LongTailJson column (contact, summary, interests, achievements, orgs, pubs, courses, custom sections, metadata). ProfileJson becomes a derived projection for legacy read paths. - DbContext: DbSets + tenant query filters + ordered indexes; FK/cascade by convention via the typed collections. - Migration hand-edited to add only the 6 new tables + LongTailJson; the scaffolder re-emitted four reconciler-owned tables (AiWorkspaceNotes, CareerProfiles, InterviewPrepNotes, CareerProfileVersions) which were stripped. The regenerated snapshot now includes them, closing the drift. Verified against a copy of the real dev DB: applies cleanly, no data loss. Long tail (achievements/orgs/pubs/courses) starts as JSON; promotable to relational later without a source-of-truth change. Source-of-truth flip stays deferred; the blob is kept as a derived projection. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
284 lines
15 KiB
C#
284 lines
15 KiB
C#
using System;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
#nullable disable
|
|
|
|
namespace JobTrackerApi.Migrations
|
|
{
|
|
/// <summary>
|
|
/// Phase 3: relational children of CareerProfile (Experience/Education/Skill/Project/
|
|
/// Certification/Language) + the LongTailJson column on CareerProfiles. See
|
|
/// docs/architecture/career-profile-model.md.
|
|
///
|
|
/// HAND-EDITED after scaffolding. `dotnet ef migrations add` also re-emitted CreateTable for
|
|
/// AiWorkspaceNotes / CareerProfiles / InterviewPrepNotes / CareerProfileVersions — all of which
|
|
/// already exist in every real database (provisioned by the F1/Phase-0 reconciler in
|
|
/// StartupInitializationExtensions, which the prior ModelSnapshot did not know). Those creates
|
|
/// were removed so the migration applies cleanly on existing databases; the regenerated
|
|
/// snapshot now includes them, closing the drift. Only the six genuinely-new child tables and
|
|
/// the new LongTailJson column remain here.
|
|
/// </summary>
|
|
public partial class AddCareerProfileRelationalChildren : Migration
|
|
{
|
|
/// <inheritdoc />
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
// New editable long-tail JSON on the existing CareerProfiles table.
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "LongTailJson",
|
|
table: "CareerProfiles",
|
|
type: "TEXT",
|
|
nullable: false,
|
|
defaultValue: "");
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerCertifications",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
|
Issuer = table.Column<string>(type: "TEXT", nullable: true),
|
|
Location = table.Column<string>(type: "TEXT", nullable: true),
|
|
Date = table.Column<string>(type: "TEXT", nullable: true),
|
|
DateNormalized = table.Column<string>(type: "TEXT", nullable: true),
|
|
DetailsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerCertifications", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerCertifications_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerEducations",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Qualification = table.Column<string>(type: "TEXT", nullable: true),
|
|
QualificationLevel = table.Column<string>(type: "TEXT", nullable: true),
|
|
Institution = table.Column<string>(type: "TEXT", nullable: true),
|
|
Location = table.Column<string>(type: "TEXT", nullable: true),
|
|
Start = table.Column<string>(type: "TEXT", nullable: true),
|
|
End = table.Column<string>(type: "TEXT", nullable: true),
|
|
StartDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
EndDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
DetailsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerEducations", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerEducations_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerExperiences",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Title = table.Column<string>(type: "TEXT", nullable: true),
|
|
Company = table.Column<string>(type: "TEXT", nullable: true),
|
|
Location = table.Column<string>(type: "TEXT", nullable: true),
|
|
Start = table.Column<string>(type: "TEXT", nullable: true),
|
|
End = table.Column<string>(type: "TEXT", nullable: true),
|
|
StartDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
EndDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
IsCurrent = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
BulletsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
SkillsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerExperiences", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerExperiences_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerLanguages",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
|
Level = table.Column<string>(type: "TEXT", nullable: true),
|
|
Notes = table.Column<string>(type: "TEXT", nullable: true),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerLanguages", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerLanguages_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerProjects",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
|
Role = table.Column<string>(type: "TEXT", nullable: true),
|
|
Location = table.Column<string>(type: "TEXT", nullable: true),
|
|
Start = table.Column<string>(type: "TEXT", nullable: true),
|
|
End = table.Column<string>(type: "TEXT", nullable: true),
|
|
StartDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
EndDate = table.Column<string>(type: "TEXT", nullable: true),
|
|
BulletsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
SkillsJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
LinksJson = table.Column<string>(type: "TEXT", nullable: false),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerProjects", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerProjects_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "CareerSkills",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
|
Category = table.Column<string>(type: "TEXT", nullable: true),
|
|
Proficiency = table.Column<string>(type: "TEXT", nullable: true),
|
|
CareerProfileId = table.Column<int>(type: "INTEGER", nullable: false),
|
|
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
|
ItemKey = table.Column<string>(type: "TEXT", nullable: false),
|
|
SortOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_CareerSkills", x => x.Id);
|
|
table.ForeignKey(
|
|
name: "FK_CareerSkills_CareerProfiles_CareerProfileId",
|
|
column: x => x.CareerProfileId,
|
|
principalTable: "CareerProfiles",
|
|
principalColumn: "Id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerCertifications_CareerProfileId",
|
|
table: "CareerCertifications",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerCertifications_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerCertifications",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerEducations_CareerProfileId",
|
|
table: "CareerEducations",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerEducations_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerEducations",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerExperiences_CareerProfileId",
|
|
table: "CareerExperiences",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerExperiences_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerExperiences",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerLanguages_CareerProfileId",
|
|
table: "CareerLanguages",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerLanguages_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerLanguages",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerProjects_CareerProfileId",
|
|
table: "CareerProjects",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerProjects_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerProjects",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerSkills_CareerProfileId",
|
|
table: "CareerSkills",
|
|
column: "CareerProfileId");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "IX_CareerSkills_OwnerUserId_CareerProfileId_SortOrder",
|
|
table: "CareerSkills",
|
|
columns: new[] { "OwnerUserId", "CareerProfileId", "SortOrder" });
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.DropTable(name: "CareerCertifications");
|
|
migrationBuilder.DropTable(name: "CareerEducations");
|
|
migrationBuilder.DropTable(name: "CareerExperiences");
|
|
migrationBuilder.DropTable(name: "CareerLanguages");
|
|
migrationBuilder.DropTable(name: "CareerProjects");
|
|
migrationBuilder.DropTable(name: "CareerSkills");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "LongTailJson",
|
|
table: "CareerProfiles");
|
|
}
|
|
}
|
|
}
|