From 2d9ffc9c2b3fb25345e5159d552453d2c3a77f7a Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 8 Jun 2026 19:56:13 +0300 Subject: [PATCH] Link pageFetcher.PageFetches to cvSearch.JobSearchSessions Adds nullable JobSearchSessionId to PageFetchEntity and FetchPageRequest. cv-search-job passes session.Id on every fetch so all Playwright page loads for a job search session can be traced back to their session. Includes index on JobSearchSessionId for efficient lookup. Co-Authored-By: Claude Sonnet 4.6 --- .../FetchPageRequest.cs | 6 ++ .../Services/PageFetcherService.cs | 1 + .../Data/Entities/PageFetchEntity.cs | 6 ++ ...08165542_AddJobSearchSessionId.Designer.cs | 88 +++++++++++++++++++ .../20260608165542_AddJobSearchSessionId.cs | 43 +++++++++ .../PageFetchDbContextModelSnapshot.cs | 6 ++ Apis/page-fetcher-data/PageFetchDbContext.cs | 3 + Jobs/cv-search-job/Tasks/CvSearchJobTask.cs | 3 +- 8 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.Designer.cs create mode 100644 Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.cs diff --git a/Apis/page-fetcher-api-models/FetchPageRequest.cs b/Apis/page-fetcher-api-models/FetchPageRequest.cs index 7a24faa..80f4e73 100644 --- a/Apis/page-fetcher-api-models/FetchPageRequest.cs +++ b/Apis/page-fetcher-api-models/FetchPageRequest.cs @@ -17,4 +17,10 @@ public sealed class FetchPageRequest /// Identifies the calling service for audit purposes (e.g. cv-matcher-api, cv-search-job). /// public string CallerService { get; set; } = string.Empty; + + /// + /// Optional reference to the job search session that triggered this fetch. + /// Stored on pageFetcher.PageFetches for cross-schema audit queries. + /// + public string? JobSearchSessionId { get; set; } } diff --git a/Apis/page-fetcher-api/Services/PageFetcherService.cs b/Apis/page-fetcher-api/Services/PageFetcherService.cs index bc7772e..df61770 100644 --- a/Apis/page-fetcher-api/Services/PageFetcherService.cs +++ b/Apis/page-fetcher-api/Services/PageFetcherService.cs @@ -101,6 +101,7 @@ public sealed class PageFetcherService Id = Guid.NewGuid().ToString("N"), Url = request.Url, CallerService = request.CallerService ?? string.Empty, + JobSearchSessionId = request.JobSearchSessionId, HttpStatusCode = statusCode, Html = html, Text = text, diff --git a/Apis/page-fetcher-data/Data/Entities/PageFetchEntity.cs b/Apis/page-fetcher-data/Data/Entities/PageFetchEntity.cs index 96ef3ef..16c08d1 100644 --- a/Apis/page-fetcher-data/Data/Entities/PageFetchEntity.cs +++ b/Apis/page-fetcher-data/Data/Entities/PageFetchEntity.cs @@ -31,4 +31,10 @@ public sealed class PageFetchEntity : BaseEntity /// Exception message when is false. public string? ErrorMessage { get; set; } + + /// + /// Optional reference to the cvSearch.JobSearchSessions row that triggered this fetch. + /// Null for fetches not originating from a job search session (e.g. direct CV-to-job matches). + /// + public string? JobSearchSessionId { get; set; } } diff --git a/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.Designer.cs b/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.Designer.cs new file mode 100644 index 0000000..a39a3db --- /dev/null +++ b/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.Designer.cs @@ -0,0 +1,88 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PageFetcher.Data; + +#nullable disable + +namespace PageFetcher.Data.Migrations +{ + [DbContext(typeof(PageFetchDbContext))] + [Migration("20260608165542_AddJobSearchSessionId")] + partial class AddJobSearchSessionId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("pageFetcher") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("PageFetcher.Data.Entities.PageFetchEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CallerService") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Html") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HttpStatusCode") + .HasColumnType("int"); + + b.Property("JobSearchSessionId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Success") + .HasColumnType("bit"); + + b.Property("Text") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("JobSearchSessionId"); + + b.HasIndex("Url"); + + b.ToTable("PageFetches", "pageFetcher"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.cs b/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.cs new file mode 100644 index 0000000..aa1ea36 --- /dev/null +++ b/Apis/page-fetcher-data/Migrations/20260608165542_AddJobSearchSessionId.cs @@ -0,0 +1,43 @@ +using PageFetcher.Data; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PageFetcher.Data.Migrations +{ + /// + public partial class AddJobSearchSessionId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "JobSearchSessionId", + schema: MigrationConstants.SchemaName, + table: "PageFetches", + type: "nvarchar(64)", + maxLength: 64, + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_PageFetches_JobSearchSessionId", + schema: MigrationConstants.SchemaName, + table: "PageFetches", + column: "JobSearchSessionId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_PageFetches_JobSearchSessionId", + schema: MigrationConstants.SchemaName, + table: "PageFetches"); + + migrationBuilder.DropColumn( + name: "JobSearchSessionId", + schema: MigrationConstants.SchemaName, + table: "PageFetches"); + } + } +} diff --git a/Apis/page-fetcher-data/Migrations/PageFetchDbContextModelSnapshot.cs b/Apis/page-fetcher-data/Migrations/PageFetchDbContextModelSnapshot.cs index dd72094..af3a679 100644 --- a/Apis/page-fetcher-data/Migrations/PageFetchDbContextModelSnapshot.cs +++ b/Apis/page-fetcher-data/Migrations/PageFetchDbContextModelSnapshot.cs @@ -53,6 +53,10 @@ namespace PageFetcher.Data.Migrations b.Property("HttpStatusCode") .HasColumnType("int"); + b.Property("JobSearchSessionId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + b.Property("Success") .HasColumnType("bit"); @@ -69,6 +73,8 @@ namespace PageFetcher.Data.Migrations b.HasIndex("CreatedAt"); + b.HasIndex("JobSearchSessionId"); + b.HasIndex("Url"); b.ToTable("PageFetches", "pageFetcher"); diff --git a/Apis/page-fetcher-data/PageFetchDbContext.cs b/Apis/page-fetcher-data/PageFetchDbContext.cs index 5f9538f..fbd9e22 100644 --- a/Apis/page-fetcher-data/PageFetchDbContext.cs +++ b/Apis/page-fetcher-data/PageFetchDbContext.cs @@ -36,8 +36,11 @@ public sealed class PageFetchDbContext : DbContext entity.Property(x => x.Html).IsRequired(); entity.Property(x => x.Text).IsRequired(); entity.Property(x => x.ErrorMessage).HasMaxLength(2000); + entity.Property(x => x.JobSearchSessionId).HasMaxLength(64); entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()"); + entity.HasIndex(x => x.JobSearchSessionId); + entity.HasIndex(x => x.Url); entity.HasIndex(x => x.CreatedAt); }); diff --git a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs index 2fc034e..43337d5 100644 --- a/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs +++ b/Jobs/cv-search-job/Tasks/CvSearchJobTask.cs @@ -169,7 +169,8 @@ public sealed class CvSearchJobTask : IJobTask { Url = url, WaitFor = "domcontentloaded", - CallerService = "cv-search-job" + CallerService = "cv-search-job", + JobSearchSessionId = session.Id }, ct); if (!fetchResponse.Success || string.IsNullOrWhiteSpace(fetchResponse.Text))