fix(cv): protect revisions and measure rendering readiness

This commit is contained in:
cesnimda
2026-10-07 07:56:35 +02:00
parent ac6d78289d
commit 046dc304aa
175 changed files with 16252 additions and 41 deletions
+7 -5
View File
@@ -28,12 +28,12 @@ public interface ICvVariantService
Task<bool> CanAssociateJobAsync(string ownerUserId, int jobApplicationId, CancellationToken ct);
Task<CvVariant?> GetAsync(string ownerUserId, int id, CancellationToken ct);
Task<CvVariant> CreateAsync(string ownerUserId, string? name, int? jobApplicationId, CvVariantSettings? settings, CancellationToken ct);
Task<CvVariant?> SaveAsync(string ownerUserId, int id, string? name, CvVariantSettings settings, string source, CancellationToken ct);
Task<CvVariant?> SaveAsync(string ownerUserId, int id, string? name, CvVariantSettings settings, string source, int expectedVersion, CancellationToken ct);
Task<CvVariant?> SetPublicAsync(string ownerUserId, int id, bool isPublic, CancellationToken ct);
Task<CvVariant?> DuplicateAsync(string ownerUserId, int id, string? newName, CancellationToken ct);
Task<bool> DeleteAsync(string ownerUserId, int id, CancellationToken ct);
Task<IReadOnlyList<CvVariantVersionInfo>> ListVersionsAsync(string ownerUserId, int id, CancellationToken ct);
Task<CvVariant?> RestoreVersionAsync(string ownerUserId, int id, int version, CancellationToken ct);
Task<CvVariant?> RestoreVersionAsync(string ownerUserId, int id, int version, int expectedVersion, CancellationToken ct);
// Render a saved variant against the current master profile.
Task<ThemedCvRenderResult?> RenderAsync(string ownerUserId, int id, CvRenderPerson person, CancellationToken ct);
@@ -108,10 +108,12 @@ public sealed class CvVariantService : ICvVariantService
return variant;
}
public async Task<CvVariant?> SaveAsync(string ownerUserId, int id, string? name, CvVariantSettings settings, string source, CancellationToken ct)
public async Task<CvVariant?> SaveAsync(string ownerUserId, int id, string? name, CvVariantSettings settings, string source, int expectedVersion, CancellationToken ct)
{
var variant = await GetAsync(ownerUserId, id, ct);
if (variant is null) return null;
if (expectedVersion < 1 || variant.Version != expectedVersion)
throw new DbUpdateConcurrencyException("The CV changed after it was loaded.");
if (!string.IsNullOrWhiteSpace(name)) variant.Name = CleanName(name);
variant.SettingsJson = CvVariantSettingsJson.Serialize(settings);
variant.Version += 1;
@@ -164,14 +166,14 @@ public sealed class CvVariantService : ICvVariantService
return versions.Select(x => new CvVariantVersionInfo(x.Version, x.Source, x.CreatedAtUtc, x.Version == variant.Version)).ToList();
}
public async Task<CvVariant?> RestoreVersionAsync(string ownerUserId, int id, int version, CancellationToken ct)
public async Task<CvVariant?> RestoreVersionAsync(string ownerUserId, int id, int version, int expectedVersion, CancellationToken ct)
{
var variant = await GetAsync(ownerUserId, id, ct);
if (variant is null) return null;
var target = await _db.CvVariantVersions.FirstOrDefaultAsync(x => x.CvVariantId == id && x.Version == version, ct);
if (target is null) return null;
var settings = CvVariantSettingsJson.Deserialize(target.SettingsJson);
return await SaveAsync(ownerUserId, id, null, settings, $"restore:v{version}", ct);
return await SaveAsync(ownerUserId, id, null, settings, $"restore:v{version}", expectedVersion, ct);
}
public async Task<ThemedCvRenderResult?> RenderAsync(string ownerUserId, int id, CvRenderPerson person, CancellationToken ct)
@@ -208,6 +208,7 @@ public sealed class PlaywrightCvPdfExporter : ICvPdfExporter
"--disable-dev-shm-usage",
"--allow-file-access-from-files",
"--enable-local-file-accesses",
"--no-pdf-header-footer",
$"--print-to-pdf={storagePath}",
new Uri(htmlPath).AbsoluteUri
};
+1 -1
View File
@@ -294,7 +294,7 @@ h1,h2{{font-family:{headingFont};}}
.section-presentation-compact>.bullets li{{margin-bottom:{F(.75 * density)}mm;}}
.section-presentation-bubble>.tags,.section-presentation-bubble>.bullets{{list-style:none;padding:0;display:flex;flex-wrap:wrap;gap:1.4mm;}}
.section-presentation-bubble>.bullets li,.section-presentation-bubble>.tags li{{margin:0;border:1px solid var(--cv-accent-soft);border-radius:999px;padding:.7mm 2.2mm;}}
.section-presentation-bubble>.bullets li::marker{{content:"";}}
.section-presentation-bubble>.bullets li::marker{{content:"""";}}
.bullets{{margin:0;padding-left:4.5mm;}}
.bullets li{{margin:0 0 {F(1.6 * density)}mm 0;}}
.bullets li::marker{{color:var(--cv-accent-color);}}