Staging to Production #51

Merged
claude merged 165 commits from main into production 2026-06-08 18:28:46 +00:00
5 changed files with 49 additions and 27 deletions
Showing only changes of commit 87de7d3f77 - Show all commits
+1 -1
View File
@@ -36,7 +36,7 @@ public sealed class CvMatcherDbContext : DbContext
entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired();
entity.Property(x => x.ResultJson).IsRequired();
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId }).IsUnique();
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId, x.Language }).IsUnique();
});
modelBuilder.Entity<CvMatcherChatCacheEntity>(entity =>
@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using CvMatcher.Data;
using Microsoft.EntityFrameworkCore;
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CvMatcher.Data.Migrations
{
[DbContext(typeof(CvMatcherDbContext))]
[Migration("20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage")]
[Migration("20260601131043_UpdateResultsUniqueConstraintToIncludeLanguage")]
partial class UpdateResultsUniqueConstraintToIncludeLanguage
{
/// <inheritdoc />
@@ -80,7 +80,7 @@ namespace CvMatcher.Data.Migrations
b.Property<string>("Language")
.IsRequired()
.HasColumnType("nvarchar(max)");
.HasColumnType("nvarchar(450)");
b.Property<string>("ResultJson")
.IsRequired()
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore.Migrations;
using CvMatcher.Data;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
@@ -11,18 +10,23 @@ namespace CvMatcher.Data.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
// The Language column was added in migration 20260524140335, but the unique constraint
// was never updated from (CvDocumentId, JobDocumentId) to include Language.
// This caused duplicate key violations when matching the same CV+Job in different languages.
migrationBuilder.DropIndex(
name: "IX_Results_CvDocumentId_JobDocumentId",
schema: MigrationConstants.SchemaName,
schema: "cvMatcher",
table: "Results");
migrationBuilder.AlterColumn<string>(
name: "Language",
schema: "cvMatcher",
table: "Results",
type: "nvarchar(450)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.CreateIndex(
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
schema: MigrationConstants.SchemaName,
schema: "cvMatcher",
table: "Results",
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
unique: true);
@@ -33,12 +37,21 @@ namespace CvMatcher.Data.Migrations
{
migrationBuilder.DropIndex(
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
schema: MigrationConstants.SchemaName,
schema: "cvMatcher",
table: "Results");
migrationBuilder.AlterColumn<string>(
name: "Language",
schema: "cvMatcher",
table: "Results",
type: "nvarchar(max)",
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(450)");
migrationBuilder.CreateIndex(
name: "IX_Results_CvDocumentId_JobDocumentId",
schema: MigrationConstants.SchemaName,
schema: "cvMatcher",
table: "Results",
columns: new[] { "CvDocumentId", "JobDocumentId" },
unique: true);
@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using CvMatcher.Data;
using Microsoft.EntityFrameworkCore;
@@ -77,7 +77,7 @@ namespace CvMatcher.Data.Migrations
b.Property<string>("Language")
.IsRequired()
.HasColumnType("nvarchar(max)");
.HasColumnType("nvarchar(450)");
b.Property<string>("ResultJson")
.IsRequired()
@@ -88,7 +88,7 @@ namespace CvMatcher.Data.Migrations
b.HasKey("Id");
b.HasIndex("CvDocumentId", "JobDocumentId")
b.HasIndex("CvDocumentId", "JobDocumentId", "Language")
.IsUnique();
b.ToTable("Results", "cvMatcher");
@@ -48,18 +48,27 @@ public sealed class EfMatcherRepository : IMatcherRepository
if (exists) return;
_db.CvMatchResults.Add(new CvMatchResultEntity
try
{
Id = Guid.NewGuid().ToString("N"),
CvDocumentId = cvDocumentId,
JobDocumentId = jobDocumentId,
Language = language,
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Score = response.Score,
CreatedAt = DateTime.UtcNow
});
_db.CvMatchResults.Add(new CvMatchResultEntity
{
Id = Guid.NewGuid().ToString("N"),
CvDocumentId = cvDocumentId,
JobDocumentId = jobDocumentId,
Language = language,
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Score = response.Score,
CreatedAt = DateTime.UtcNow
});
await _db.SaveChangesAsync(ct);
await _db.SaveChangesAsync(ct);
}
catch (DbUpdateException ex) when (ex.InnerException?.Message.Contains("IX_Results_CvDocumentId_JobDocumentId_Language") == true
|| ex.InnerException?.Message.Contains("unique") == true)
{
// Duplicate key violation: record was inserted between the AnyAsync check and SaveChangesAsync.
// This is safe to ignore — the match result already exists in the database.
}
}
public async Task<string?> GetChatCompletionAsync(string cacheKey, CancellationToken ct)