feat: add career profile foundation with versioned history
Introduces the Career Workspace's bounded data foundation, additive and backwards-compatible: ApplicationUser.ProfileCvStructureJson stays the authoritative column every existing read path uses; the new CareerProfiles/CareerProfileVersions tables mirror it via ICareerProfileService so future Career Workspace features (variants, history UI) have real tables to build on rather than starting a second migration later. - CareerProfile: one snapshot row per user (Version, ProfileJson). - CareerProfileVersion: append-only history, one row per save (upload/rebuild/improve/reprocess/parse), so a profile edit is never silently lost the way ProfileCvStructureJson overwrites are today. - Stable item IDs assigned to jobs/education/certifications/projects on first save and preserved across later saves -- the prerequisite for CV variants to reference "this job" by identity instead of array position. - CvDateNormalizer: best-effort free-string -> "YYYY-MM" parsing for job/education/certification/project date ranges, kept alongside (never replacing) the original free-string fields. - Both SQLite (dev) and MySQL/MariaDB (prod) reconciler dialects, matching this repo's schema-via-raw-SQL-reconciler convention rather than EF migrations. Job tracking is untouched -- this is entirely within the profile/CV domain per the Career Workspace product boundary.
This commit is contained in:
@@ -28,6 +28,8 @@ namespace JobTrackerApi.Data
|
||||
public DbSet<CvUploadArtifact> CvUploadArtifacts => Set<CvUploadArtifact>();
|
||||
public DbSet<CvExtractionRun> CvExtractionRuns => Set<CvExtractionRun>();
|
||||
public DbSet<TailoredCvDraft> TailoredCvDrafts => Set<TailoredCvDraft>();
|
||||
public DbSet<CareerProfile> CareerProfiles => Set<CareerProfile>();
|
||||
public DbSet<CareerProfileVersion> CareerProfileVersions => Set<CareerProfileVersion>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
@@ -141,6 +143,27 @@ namespace JobTrackerApi.Data
|
||||
.WithOne(j => j.TailoredCvDraft)
|
||||
.HasForeignKey<TailoredCvDraft>(x => x.JobApplicationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// Career Workspace foundation (docs/career-workspace-implementation-roadmap.md Phase F1).
|
||||
// One CareerProfile per user for now -- see roadmap "Not now: multiple profiles per user".
|
||||
modelBuilder.Entity<CareerProfile>()
|
||||
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
|
||||
|
||||
modelBuilder.Entity<CareerProfile>()
|
||||
.HasIndex(x => x.OwnerUserId)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<CareerProfileVersion>()
|
||||
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
|
||||
|
||||
modelBuilder.Entity<CareerProfileVersion>()
|
||||
.HasIndex(x => new { x.OwnerUserId, x.CareerProfileId, x.Version });
|
||||
|
||||
modelBuilder.Entity<CareerProfileVersion>()
|
||||
.HasOne(x => x.CareerProfile)
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.CareerProfileId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user