feat(cv): rebuild professional resume studio
This commit is contained in:
@@ -4,7 +4,18 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Services;
|
||||
|
||||
public sealed record CvVariantSummary(int Id, string Name, string ThemeId, string PublicSlug, bool IsPublic, int Version, int? JobApplicationId, DateTimeOffset UpdatedAtUtc);
|
||||
public sealed record CvVariantSummary(
|
||||
int Id,
|
||||
string Name,
|
||||
string ThemeId,
|
||||
string Language,
|
||||
string PublicSlug,
|
||||
bool IsPublic,
|
||||
int Version,
|
||||
int? JobApplicationId,
|
||||
DateTimeOffset UpdatedAtUtc,
|
||||
string? JobTitle = null,
|
||||
string? CompanyName = null);
|
||||
public sealed record CvVariantVersionInfo(int Version, string Source, DateTimeOffset CreatedAtUtc, bool IsCurrent);
|
||||
|
||||
// Person + photo needed to render, resolved from ApplicationUser by the controller so this service
|
||||
@@ -14,6 +25,7 @@ public sealed record CvRenderPerson(string FallbackName, string? PhotoDataUrl);
|
||||
public interface ICvVariantService
|
||||
{
|
||||
Task<IReadOnlyList<CvVariantSummary>> ListAsync(string ownerUserId, CancellationToken ct);
|
||||
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);
|
||||
@@ -49,18 +61,26 @@ public sealed class CvVariantService : ICvVariantService
|
||||
|
||||
public async Task<IReadOnlyList<CvVariantSummary>> ListAsync(string ownerUserId, CancellationToken ct)
|
||||
{
|
||||
var query = _db.CvVariants.Where(x => x.OwnerUserId == ownerUserId);
|
||||
var query = _db.CvVariants
|
||||
.Include(x => x.JobApplication)
|
||||
.ThenInclude(x => x!.Company)
|
||||
.Where(x => x.OwnerUserId == ownerUserId);
|
||||
var variants = _db.Database.IsSqlite()
|
||||
? (await query.ToListAsync(ct)).OrderByDescending(x => x.UpdatedAtUtc).ToList()
|
||||
: await query.OrderByDescending(x => x.UpdatedAtUtc).ToListAsync(ct);
|
||||
return variants.Select(Summarize).ToList();
|
||||
}
|
||||
|
||||
public Task<bool> CanAssociateJobAsync(string ownerUserId, int jobApplicationId, CancellationToken ct) =>
|
||||
_db.JobApplications.AnyAsync(job => job.Id == jobApplicationId && job.OwnerUserId == ownerUserId, ct);
|
||||
|
||||
public Task<CvVariant?> GetAsync(string ownerUserId, int id, CancellationToken ct) =>
|
||||
_db.CvVariants.FirstOrDefaultAsync(x => x.Id == id && x.OwnerUserId == ownerUserId, ct);
|
||||
|
||||
public async Task<CvVariant> CreateAsync(string ownerUserId, string? name, int? jobApplicationId, CvVariantSettings? settings, CancellationToken ct)
|
||||
{
|
||||
if (jobApplicationId is not null && !await CanAssociateJobAsync(ownerUserId, jobApplicationId.Value, ct))
|
||||
throw new ArgumentException("The job application is unavailable.", nameof(jobApplicationId));
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var normalized = CvVariantSettingsJson.Normalize(settings);
|
||||
var variant = new CvVariant
|
||||
@@ -110,7 +130,11 @@ public sealed class CvVariantService : ICvVariantService
|
||||
var source = await GetAsync(ownerUserId, id, ct);
|
||||
if (source is null) return null;
|
||||
var settings = CvVariantSettingsJson.Deserialize(source.SettingsJson);
|
||||
return await CreateAsync(ownerUserId, string.IsNullOrWhiteSpace(newName) ? $"{source.Name} (copy)" : newName, source.JobApplicationId, settings, ct);
|
||||
var associatedJobId = source.JobApplicationId is not null
|
||||
&& await CanAssociateJobAsync(ownerUserId, source.JobApplicationId.Value, ct)
|
||||
? source.JobApplicationId
|
||||
: null;
|
||||
return await CreateAsync(ownerUserId, string.IsNullOrWhiteSpace(newName) ? $"{source.Name} (copy)" : newName, associatedJobId, settings, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(string ownerUserId, int id, CancellationToken ct)
|
||||
@@ -202,7 +226,18 @@ public sealed class CvVariantService : ICvVariantService
|
||||
private static CvVariantSummary Summarize(CvVariant v)
|
||||
{
|
||||
var settings = CvVariantSettingsJson.Deserialize(v.SettingsJson);
|
||||
return new CvVariantSummary(v.Id, v.Name, settings.ThemeId, v.PublicSlug, v.IsPublic, v.Version, v.JobApplicationId, v.UpdatedAtUtc);
|
||||
return new CvVariantSummary(
|
||||
v.Id,
|
||||
v.Name,
|
||||
settings.ThemeId,
|
||||
settings.Language ?? "en",
|
||||
v.PublicSlug,
|
||||
v.IsPublic,
|
||||
v.Version,
|
||||
v.JobApplicationId,
|
||||
v.UpdatedAtUtc,
|
||||
v.JobApplication?.JobTitle,
|
||||
v.JobApplication?.Company?.Name);
|
||||
}
|
||||
|
||||
private static string CleanName(string? name) => string.IsNullOrWhiteSpace(name) ? "Untitled CV" : name.Trim();
|
||||
|
||||
Reference in New Issue
Block a user