fix: close release preflight gaps
CI and Deploy / test (pull_request) Failing after 1m22s
CI and Deploy / deploy (pull_request) Has been skipped

Route public health checks to the API, backfill and synchronize job opportunities, stabilize SPA smoke tests, and document operator-only production steps.
This commit is contained in:
cesnimda
2026-07-31 20:18:30 +02:00
parent ce76046a29
commit 955182b7c2
18 changed files with 350 additions and 52 deletions
@@ -0,0 +1,52 @@
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using Microsoft.EntityFrameworkCore;
namespace JobTrackerApi.Services;
public static class JobOpportunitySync
{
public static Job Create(JobApplication application, string? source = null, string? countryCode = null)
{
var opportunity = new Job { Source = source, CountryCode = countryCode };
Apply(application, opportunity);
return opportunity;
}
public static void Apply(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;
}
public static async Task<int> BackfillLegacyAsync(JobTrackerContext db, CancellationToken cancellationToken = default)
{
var applications = await db.JobApplications
.IgnoreQueryFilters()
.Where(application => application.JobId == null)
.ToListAsync(cancellationToken);
foreach (var application in applications)
application.Job = Create(application, "legacy");
if (applications.Count > 0)
await db.SaveChangesAsync(cancellationToken);
return applications.Count;
}
}
@@ -2111,8 +2111,9 @@ public static class StartupInitializationExtensions
var companyOwnershipExists = ColumnExists(conn, provider, "Companies", "OwnerUserId");
var jobOwnershipExists = ColumnExists(conn, provider, "JobApplications", "OwnerUserId");
var opportunityOwnershipExists = ColumnExists(conn, provider, "Jobs", "OwnerUserId");
if (companyOwnershipExists || jobOwnershipExists)
if (companyOwnershipExists || jobOwnershipExists || opportunityOwnershipExists)
{
if (companyOwnershipExists)
{
@@ -2123,6 +2124,11 @@ public static class StartupInitializationExtensions
{
adminDb.Database.ExecuteSqlRaw("UPDATE JobApplications SET OwnerUserId = {0} WHERE OwnerUserId IS NULL;", admin.Id);
}
if (opportunityOwnershipExists)
{
adminDb.Database.ExecuteSqlRaw("UPDATE Jobs SET OwnerUserId = {0} WHERE OwnerUserId IS NULL;", admin.Id);
}
}
}
catch (Exception ex)
@@ -2173,6 +2179,14 @@ public static class StartupInitializationExtensions
}
}
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<JobTrackerContext>();
var backfilled = JobOpportunitySync.BackfillLegacyAsync(db).GetAwaiter().GetResult();
if (backfilled > 0)
app.Logger.LogInformation("Backfilled {Count} legacy job opportunities.", backfilled);
}
var readiness = app.Services.GetRequiredService<IStartupReadiness>();
readiness.MarkReady();