176 lines
8.1 KiB
C#
176 lines
8.1 KiB
C#
using System.Text.Json;
|
|
using JobTrackerApi.Data;
|
|
using JobTrackerApi.Models;
|
|
|
|
namespace JobTrackerApi.Services;
|
|
|
|
// Phase 3: two-way projection between the relational CareerProfile children and the
|
|
// StructuredCvProfile shape (the derived blob + the frontend/AI edit unit).
|
|
// See docs/architecture/career-profile-model.md.
|
|
//
|
|
// Relational holds: Experience, Education, Skill, Project, Certification, Language.
|
|
// LongTailJson holds the rest of StructuredCvProfile: Contact, Summary, Interests, OtherSections,
|
|
// Sections, Metadata (and future achievements/orgs/pubs/courses).
|
|
public static class CareerProfileMapper
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
/// <summary>The non-relational parts of a StructuredCvProfile, stored as CareerProfile.LongTailJson.</summary>
|
|
public sealed class CareerLongTail
|
|
{
|
|
public string Version { get; set; } = "1";
|
|
public StructuredCvMetadata Metadata { get; set; } = new();
|
|
public StructuredCvContact Contact { get; set; } = new();
|
|
public List<string> Summary { get; set; } = new();
|
|
public List<string> Interests { get; set; } = new();
|
|
public List<string> Awards { get; set; } = new();
|
|
public List<string> Publications { get; set; } = new();
|
|
public List<string> Organisations { get; set; } = new();
|
|
public List<string> References { get; set; } = new();
|
|
public List<StructuredCvOtherSection> OtherSections { get; set; } = new();
|
|
public List<StructuredCvSection> Sections { get; set; } = new();
|
|
}
|
|
|
|
public static string SerializeLongTail(StructuredCvProfile p)
|
|
=> JsonSerializer.Serialize(new CareerLongTail
|
|
{
|
|
Version = p.Version,
|
|
Metadata = p.Metadata,
|
|
Contact = p.Contact,
|
|
Summary = p.Summary,
|
|
Interests = p.Interests,
|
|
Awards = p.Awards,
|
|
Publications = p.Publications,
|
|
Organisations = p.Organisations,
|
|
References = p.References,
|
|
OtherSections = p.OtherSections,
|
|
Sections = p.Sections,
|
|
}, JsonOptions);
|
|
|
|
private static CareerLongTail DeserializeLongTail(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json)) return new CareerLongTail();
|
|
try { return JsonSerializer.Deserialize<CareerLongTail>(json, JsonOptions) ?? new CareerLongTail(); }
|
|
catch { return new CareerLongTail(); }
|
|
}
|
|
|
|
// --- StructuredCvProfile -> relational rows (insert) ------------------------------------------
|
|
|
|
public static void AddChildren(JobTrackerContext db, int careerProfileId, string ownerUserId, StructuredCvProfile p)
|
|
{
|
|
int order = 0;
|
|
foreach (var j in p.Jobs)
|
|
db.CareerExperiences.Add(new CareerExperience
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(j.Id), SortOrder = order++,
|
|
Title = j.Title, Company = j.Company, Location = j.Location,
|
|
Start = j.Start, End = j.End, StartDate = j.StartDate, EndDate = j.EndDate, IsCurrent = j.IsCurrent,
|
|
Bullets = j.Bullets, Skills = j.Skills,
|
|
});
|
|
|
|
order = 0;
|
|
foreach (var e in p.Education)
|
|
db.CareerEducations.Add(new CareerEducation
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(e.Id), SortOrder = order++,
|
|
Qualification = e.Qualification, QualificationLevel = e.QualificationLevel, Institution = e.Institution,
|
|
Location = e.Location, Start = e.Start, End = e.End, StartDate = e.StartDate, EndDate = e.EndDate,
|
|
Details = e.Details,
|
|
});
|
|
|
|
order = 0;
|
|
foreach (var s in p.Skills)
|
|
db.CareerSkills.Add(new CareerSkill
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(null), SortOrder = order++,
|
|
Name = s,
|
|
});
|
|
|
|
order = 0;
|
|
foreach (var pr in p.Projects)
|
|
db.CareerProjects.Add(new CareerProject
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(pr.Id), SortOrder = order++,
|
|
Name = pr.Name, Role = pr.Role, Location = pr.Location,
|
|
Start = pr.Start, End = pr.End, StartDate = pr.StartDate, EndDate = pr.EndDate,
|
|
Bullets = pr.Bullets, Skills = pr.Skills,
|
|
});
|
|
|
|
order = 0;
|
|
foreach (var c in p.Certifications)
|
|
db.CareerCertifications.Add(new CareerCertification
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(c.Id), SortOrder = order++,
|
|
Name = c.Name, Issuer = c.Issuer, Location = c.Location, Date = c.Date, DateNormalized = c.DateNormalized,
|
|
Details = c.Details,
|
|
});
|
|
|
|
order = 0;
|
|
foreach (var l in p.Languages)
|
|
db.CareerLanguages.Add(new CareerLanguage
|
|
{
|
|
CareerProfileId = careerProfileId, OwnerUserId = ownerUserId, ItemKey = KeyOf(null), SortOrder = order++,
|
|
Name = l.Name, Level = l.Level, Notes = l.Notes,
|
|
});
|
|
}
|
|
|
|
// --- relational rows -> StructuredCvProfile (read) -------------------------------------------
|
|
|
|
public static StructuredCvProfile ToStructured(
|
|
string? longTailJson,
|
|
List<CareerExperience> experiences,
|
|
List<CareerEducation> education,
|
|
List<CareerSkill> skills,
|
|
List<CareerProject> projects,
|
|
List<CareerCertification> certifications,
|
|
List<CareerLanguage> languages)
|
|
{
|
|
var tail = DeserializeLongTail(longTailJson);
|
|
return new StructuredCvProfile
|
|
{
|
|
Version = tail.Version,
|
|
Metadata = tail.Metadata,
|
|
Contact = tail.Contact,
|
|
Summary = tail.Summary,
|
|
Interests = tail.Interests,
|
|
Awards = tail.Awards,
|
|
Publications = tail.Publications,
|
|
Organisations = tail.Organisations,
|
|
References = tail.References,
|
|
OtherSections = tail.OtherSections,
|
|
Sections = tail.Sections,
|
|
Jobs = experiences.Select(x => new StructuredCvJob
|
|
{
|
|
Id = NullIfEmpty(x.ItemKey), Title = x.Title, Company = x.Company, Location = x.Location,
|
|
Start = x.Start, End = x.End, StartDate = x.StartDate, EndDate = x.EndDate, IsCurrent = x.IsCurrent,
|
|
Bullets = x.Bullets, Skills = x.Skills,
|
|
}).ToList(),
|
|
Education = education.Select(x => new StructuredCvEducation
|
|
{
|
|
Id = NullIfEmpty(x.ItemKey), Qualification = x.Qualification, QualificationLevel = x.QualificationLevel,
|
|
Institution = x.Institution, Location = x.Location, Start = x.Start, End = x.End,
|
|
StartDate = x.StartDate, EndDate = x.EndDate, Details = x.Details,
|
|
}).ToList(),
|
|
Skills = skills.Select(x => x.Name ?? string.Empty).Where(x => x.Length > 0).ToList(),
|
|
Projects = projects.Select(x => new StructuredCvProject
|
|
{
|
|
Id = NullIfEmpty(x.ItemKey), Name = x.Name, Role = x.Role, Location = x.Location,
|
|
Start = x.Start, End = x.End, StartDate = x.StartDate, EndDate = x.EndDate,
|
|
Bullets = x.Bullets, Skills = x.Skills,
|
|
}).ToList(),
|
|
Certifications = certifications.Select(x => new StructuredCvCertification
|
|
{
|
|
Id = NullIfEmpty(x.ItemKey), Name = x.Name, Issuer = x.Issuer, Location = x.Location,
|
|
Date = x.Date, DateNormalized = x.DateNormalized, Details = x.Details,
|
|
}).ToList(),
|
|
Languages = languages.Select(x => new StructuredCvLanguage
|
|
{
|
|
Name = x.Name, Level = x.Level, Notes = x.Notes,
|
|
}).ToList(),
|
|
};
|
|
}
|
|
|
|
private static string KeyOf(string? id) => string.IsNullOrWhiteSpace(id) ? Guid.NewGuid().ToString("N")[..12] : id;
|
|
private static string? NullIfEmpty(string? s) => string.IsNullOrWhiteSpace(s) ? null : s;
|
|
}
|