fix: keep opportunity data synchronized
CI and Deploy / test (push) Successful in 2m46s
CI and Deploy / deploy (push) Successful in 54s

This commit is contained in:
cesnimda
2026-07-31 00:12:10 +02:00
parent 10d548f799
commit af3cf1cdaa
2 changed files with 32 additions and 23 deletions
@@ -239,6 +239,7 @@ public sealed class JobApplicationsEndpointBehaviorTests
SalaryMax = 60000m, SalaryMax = 60000m,
SalaryCurrency = "NOK", SalaryCurrency = "NOK",
SalaryPeriod = "year", SalaryPeriod = "year",
Job = new Job { CompanyId = company.Id, JobTitle = "Backend Dev", Source = "nav", CountryCode = "NO" },
}; };
db.JobApplications.Add(job); db.JobApplications.Add(job);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
@@ -278,6 +279,12 @@ public sealed class JobApplicationsEndpointBehaviorTests
Assert.Null(saved.SalaryMax); Assert.Null(saved.SalaryMax);
Assert.Null(saved.SalaryCurrency); Assert.Null(saved.SalaryCurrency);
Assert.Null(saved.SalaryPeriod); Assert.Null(saved.SalaryPeriod);
var opportunity = await db.Jobs.SingleAsync();
Assert.Equal(saved.JobTitle, opportunity.JobTitle);
Assert.Null(opportunity.SalaryMin);
Assert.Null(opportunity.SalaryPeriod);
Assert.Equal("nav", opportunity.Source);
Assert.Equal("NO", opportunity.CountryCode);
} }
private static JobApplicationsController CreateController(JobTrackerContext db, string userId) private static JobApplicationsController CreateController(JobTrackerContext db, string userId)
@@ -707,6 +707,27 @@ Canonical profile:
return Ok(dtos); return Ok(dtos);
} }
private static void SyncOpportunity(JobApplication application, Job opportunity)
{
opportunity.OwnerUserId = application.OwnerUserId;
opportunity.CompanyId = application.CompanyId;
opportunity.JobTitle = application.JobTitle;
opportunity.Location = application.Location;
opportunity.JobUrl = application.JobUrl;
opportunity.Description = application.Description;
opportunity.TranslatedDescription = application.TranslatedDescription;
opportunity.DescriptionLanguage = application.DescriptionLanguage;
opportunity.ShortSummary = application.ShortSummary;
opportunity.Tags = application.Tags;
opportunity.Deadline = application.Deadline;
opportunity.Salary = application.Salary;
opportunity.SalaryMin = application.SalaryMin;
opportunity.SalaryMax = application.SalaryMax;
opportunity.SalaryCurrency = application.SalaryCurrency;
opportunity.SalaryPeriod = application.SalaryPeriod;
opportunity.SavedAt = application.SavedAt;
}
private static (decimal? Min, decimal? Max, string? Currency, string? Period) NormalizeSalary( private static (decimal? Min, decimal? Max, string? Currency, string? Period) NormalizeSalary(
decimal? min, decimal? max, string? currency, string? period) decimal? min, decimal? max, string? currency, string? period)
{ {
@@ -766,23 +787,7 @@ Canonical profile:
if (source?.Length > 32) source = source[..32]; if (source?.Length > 32) source = source[..32];
var countryCode = string.IsNullOrWhiteSpace(request.CountryCode) ? null : request.CountryCode.Trim().ToUpperInvariant(); var countryCode = string.IsNullOrWhiteSpace(request.CountryCode) ? null : request.CountryCode.Trim().ToUpperInvariant();
if (countryCode?.Length != 2) countryCode = null; if (countryCode?.Length != 2) countryCode = null;
job.Job = new Job job.Job = new Job { Source = source, CountryCode = countryCode };
{
OwnerUserId = job.OwnerUserId,
CompanyId = job.CompanyId,
JobTitle = job.JobTitle,
Location = job.Location,
JobUrl = job.JobUrl,
Description = job.Description,
TranslatedDescription = job.TranslatedDescription,
DescriptionLanguage = job.DescriptionLanguage,
Tags = job.Tags,
Deadline = job.Deadline,
Salary = job.Salary,
Source = source,
CountryCode = countryCode,
SavedAt = job.SavedAt,
};
// A job created straight into a pre-application stage has not been applied to, so it // A job created straight into a pre-application stage has not been applied to, so it
// must not carry an applied date. SyncAppliedDate also covers the reverse: a create // must not carry an applied date. SyncAppliedDate also covers the reverse: a create
@@ -791,23 +796,19 @@ Canonical profile:
(job.SalaryMin, job.SalaryMax, job.SalaryCurrency, job.SalaryPeriod) = (job.SalaryMin, job.SalaryMax, job.SalaryCurrency, job.SalaryPeriod) =
NormalizeSalary(request.SalaryMin, request.SalaryMax, request.SalaryCurrency, request.SalaryPeriod); NormalizeSalary(request.SalaryMin, request.SalaryMax, request.SalaryCurrency, request.SalaryPeriod);
job.Job.SalaryMin = job.SalaryMin;
job.Job.SalaryMax = job.SalaryMax;
job.Job.SalaryCurrency = job.SalaryCurrency;
job.Job.SalaryPeriod = job.SalaryPeriod;
// Generate and persist a short summary at creation time to avoid repeated model calls. // Generate and persist a short summary at creation time to avoid repeated model calls.
try try
{ {
var shortSum = await _summarizer.SummarizeAsync(BuildSummarySource(job), 160, 60); var shortSum = await _summarizer.SummarizeAsync(BuildSummarySource(job), 160, 60);
job.ShortSummary = shortSum; job.ShortSummary = shortSum;
job.Job.ShortSummary = shortSum;
} }
catch catch
{ {
// ignore summarizer failures at create time // ignore summarizer failures at create time
} }
SyncOpportunity(job, job.Job);
_db.JobApplications.Add(job); _db.JobApplications.Add(job);
await _db.SaveChangesAsync(cancellationToken); await _db.SaveChangesAsync(cancellationToken);
@@ -830,7 +831,7 @@ Canonical profile:
[HttpPut("{id:int}")] [HttpPut("{id:int}")]
public async Task<IActionResult> Update([FromRoute] int id, [FromBody] UpdateJobApplicationRequest request, CancellationToken cancellationToken) public async Task<IActionResult> Update([FromRoute] int id, [FromBody] UpdateJobApplicationRequest request, CancellationToken cancellationToken)
{ {
var job = await _db.JobApplications.FirstOrDefaultAsync(j => j.Id == id, cancellationToken); var job = await _db.JobApplications.Include(j => j.Job).FirstOrDefaultAsync(j => j.Id == id, cancellationToken);
if (job is null) return NotFound(); if (job is null) return NotFound();
var oldStatus = job.Status; var oldStatus = job.Status;
@@ -881,6 +882,7 @@ Canonical profile:
// Records StatusChanged plus any lifecycle event the transition implies. // Records StatusChanged plus any lifecycle event the transition implies.
JobLifecycleEvents.RecordStatusChange(_db, job, oldStatus, request.StatusChangedAt ?? DateTime.Now); JobLifecycleEvents.RecordStatusChange(_db, job, oldStatus, request.StatusChangedAt ?? DateTime.Now);
if (job.Job is not null) SyncOpportunity(job, job.Job);
await _db.SaveChangesAsync(cancellationToken); await _db.SaveChangesAsync(cancellationToken);
return NoContent(); return NoContent();
} }