diff --git a/JobTrackerApi.Tests/JobApplicationsEndpointBehaviorTests.cs b/JobTrackerApi.Tests/JobApplicationsEndpointBehaviorTests.cs index 5020edb..7e94e1a 100644 --- a/JobTrackerApi.Tests/JobApplicationsEndpointBehaviorTests.cs +++ b/JobTrackerApi.Tests/JobApplicationsEndpointBehaviorTests.cs @@ -239,6 +239,7 @@ public sealed class JobApplicationsEndpointBehaviorTests SalaryMax = 60000m, SalaryCurrency = "NOK", SalaryPeriod = "year", + Job = new Job { CompanyId = company.Id, JobTitle = "Backend Dev", Source = "nav", CountryCode = "NO" }, }; db.JobApplications.Add(job); await db.SaveChangesAsync(); @@ -278,6 +279,12 @@ public sealed class JobApplicationsEndpointBehaviorTests Assert.Null(saved.SalaryMax); Assert.Null(saved.SalaryCurrency); 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) diff --git a/JobTrackerApi/Controllers/JobApplicationsController.cs b/JobTrackerApi/Controllers/JobApplicationsController.cs index 8641ea9..ff910ed 100644 --- a/JobTrackerApi/Controllers/JobApplicationsController.cs +++ b/JobTrackerApi/Controllers/JobApplicationsController.cs @@ -707,6 +707,27 @@ Canonical profile: 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( decimal? min, decimal? max, string? currency, string? period) { @@ -766,23 +787,7 @@ Canonical profile: if (source?.Length > 32) source = source[..32]; var countryCode = string.IsNullOrWhiteSpace(request.CountryCode) ? null : request.CountryCode.Trim().ToUpperInvariant(); if (countryCode?.Length != 2) countryCode = null; - job.Job = new Job - { - 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, - }; + job.Job = new Job { Source = source, CountryCode = countryCode }; // 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 @@ -791,23 +796,19 @@ Canonical profile: (job.SalaryMin, job.SalaryMax, job.SalaryCurrency, job.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. try { var shortSum = await _summarizer.SummarizeAsync(BuildSummarySource(job), 160, 60); job.ShortSummary = shortSum; - job.Job.ShortSummary = shortSum; } catch { // ignore summarizer failures at create time } + SyncOpportunity(job, job.Job); _db.JobApplications.Add(job); await _db.SaveChangesAsync(cancellationToken); @@ -830,7 +831,7 @@ Canonical profile: [HttpPut("{id:int}")] public async Task 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(); var oldStatus = job.Status; @@ -881,6 +882,7 @@ Canonical profile: // Records StatusChanged plus any lifecycle event the transition implies. JobLifecycleEvents.RecordStatusChange(_db, job, oldStatus, request.StatusChangedAt ?? DateTime.Now); + if (job.Job is not null) SyncOpportunity(job, job.Job); await _db.SaveChangesAsync(cancellationToken); return NoContent(); }