feat: complete phase 3 career workspace
CI and Deploy / test (push) Failing after 1m6s
CI and Deploy / deploy (push) Has been skipped

This commit is contained in:
cesnimda
2026-07-30 22:19:13 +02:00
parent f8a7cf5205
commit 4cf26405f6
18 changed files with 215 additions and 20 deletions
+37
View File
@@ -0,0 +1,37 @@
using System.Security.Cryptography;
using System.Text;
namespace JobTrackerApi.Services;
public static class AvatarStorage
{
private const string FilePrefix = "file:";
public static async Task<string> StoreAsync(string dataRoot, string userId, byte[] bytes, string contentType, CancellationToken cancellationToken)
{
var userKey = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(userId))).ToLowerInvariant();
var folder = Path.Combine(dataRoot, "Avatars", userKey);
Directory.CreateDirectory(folder);
var extension = contentType switch { "image/png" => ".png", "image/webp" => ".webp", _ => ".jpg" };
var path = Path.Combine(folder, "avatar" + extension);
foreach (var old in Directory.EnumerateFiles(folder, "avatar.*")) if (!string.Equals(old, path, StringComparison.OrdinalIgnoreCase)) File.Delete(old);
await File.WriteAllBytesAsync(path, bytes, cancellationToken);
return FilePrefix + path;
}
public static string? Resolve(string? value)
{
if (string.IsNullOrWhiteSpace(value) || !value.StartsWith(FilePrefix, StringComparison.Ordinal)) return value;
var path = value[FilePrefix.Length..];
if (!File.Exists(path)) return null;
var contentType = Path.GetExtension(path).ToLowerInvariant() switch { ".png" => "image/png", ".webp" => "image/webp", _ => "image/jpeg" };
return $"data:{contentType};base64,{Convert.ToBase64String(File.ReadAllBytes(path))}";
}
public static void Delete(string? value)
{
if (value?.StartsWith(FilePrefix, StringComparison.Ordinal) != true) return;
var path = value[FilePrefix.Length..];
if (File.Exists(path)) File.Delete(path);
}
}
@@ -23,6 +23,10 @@ public static class CareerProfileMapper
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();
}
@@ -35,6 +39,10 @@ public static class CareerProfileMapper
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);
@@ -125,6 +133,10 @@ public static class CareerProfileMapper
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
+8
View File
@@ -71,6 +71,10 @@ public sealed class CvProfileDiffService : ICvProfileDiffService
DiffLanguages(current.Languages, extracted.Languages),
DiffScalars("Skills", current.Skills, extracted.Skills),
DiffScalars("Interests", current.Interests, extracted.Interests),
DiffScalars("Awards", current.Awards, extracted.Awards),
DiffScalars("Publications", current.Publications, extracted.Publications),
DiffScalars("Organisations", current.Organisations, extracted.Organisations),
DiffScalars("References", current.References, extracted.References),
}.Where(c => c is not null).Select(c => c!).ToList(),
};
}
@@ -89,6 +93,10 @@ public sealed class CvProfileDiffService : ICvProfileDiffService
MergeLanguages(merged.Languages, extracted.Languages);
AppendUnique(merged.Skills, extracted.Skills);
AppendUnique(merged.Interests, extracted.Interests);
AppendUnique(merged.Awards, extracted.Awards);
AppendUnique(merged.Publications, extracted.Publications);
AppendUnique(merged.Organisations, extracted.Organisations);
AppendUnique(merged.References, extracted.References);
MergeList(merged.OtherSections, extracted.OtherSections, x => Norm(x.Title), (a, b) => AppendUnique(a.Items, b.Items));
if (extracted.Sections.Count > 0) merged.Sections = extracted.Sections;
foreach (var field in extracted.Metadata.Fields) merged.Metadata.Fields[field.Key] = field.Value;
+4
View File
@@ -79,6 +79,10 @@ public static class CvVariantResolver
["certifications"] = CertificationSection(profile, settings),
["languages"] = LanguageSection(profile),
["interests"] = TagSection("interests", "Interests", profile.Interests),
["awards"] = BulletSection("awards", "Awards", profile.Awards),
["publications"] = BulletSection("publications", "Publications", profile.Publications),
["organisations"] = BulletSection("organisations", "Organisations", profile.Organisations),
["references"] = BulletSection("references", "References", profile.References),
};
// OtherSections from the master profile become body sections keyed other:<n>.