diff --git a/Apis/api/Controllers/CvMatcherController.cs b/Apis/api/Controllers/CvMatcherController.cs index 1111b78..03098f2 100644 --- a/Apis/api/Controllers/CvMatcherController.cs +++ b/Apis/api/Controllers/CvMatcherController.cs @@ -181,7 +181,7 @@ public sealed class CvMatcherController : ControllerBase try { var tokenResp = await _jobSearchApi.CreateTokenAsync( - new CreateJobSearchTokenRequest { CvDocumentId = request.CvDocumentId, Email = request.Email, Language = language }, + new CreateJobSearchTokenRequest { CvDocumentId = request.CvDocumentId, Email = request.Email, Language = language, Keywords = res.Keywords }, ct); if (!string.IsNullOrWhiteSpace(tokenResp.TokenId)) { diff --git a/Apis/cv-matcher-api-models/Requests/CreateJobSearchTokenRequest.cs b/Apis/cv-matcher-api-models/Requests/CreateJobSearchTokenRequest.cs index a496b0c..6e1bcb4 100644 --- a/Apis/cv-matcher-api-models/Requests/CreateJobSearchTokenRequest.cs +++ b/Apis/cv-matcher-api-models/Requests/CreateJobSearchTokenRequest.cs @@ -5,4 +5,5 @@ public sealed class CreateJobSearchTokenRequest public string CvDocumentId { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string Language { get; set; } = "en"; + public List Keywords { get; set; } = []; } diff --git a/Apis/cv-matcher-api-models/Responses/JobMatchResponse.cs b/Apis/cv-matcher-api-models/Responses/JobMatchResponse.cs index 8ef3e1d..b7ffe7b 100644 --- a/Apis/cv-matcher-api-models/Responses/JobMatchResponse.cs +++ b/Apis/cv-matcher-api-models/Responses/JobMatchResponse.cs @@ -8,6 +8,7 @@ public List Gaps { get; set; } = []; public List Recommendations { get; set; } = []; public List Evidence { get; set; } = []; + public List Keywords { get; set; } = []; public bool Cached { get; set; } public string? JobDocumentId { get; set; } public string? JobUrl { get; set; } diff --git a/Apis/cv-matcher-api/Controllers/JobSearchController.cs b/Apis/cv-matcher-api/Controllers/JobSearchController.cs index 65bd1eb..c44e7a5 100644 --- a/Apis/cv-matcher-api/Controllers/JobSearchController.cs +++ b/Apis/cv-matcher-api/Controllers/JobSearchController.cs @@ -53,7 +53,7 @@ public sealed class JobSearchController : ControllerBase if (string.IsNullOrWhiteSpace(request.CvDocumentId) || string.IsNullOrWhiteSpace(request.Email)) return BadRequest(new ErrorResponse { Error = "CvDocumentId and Email are required.", Code = "invalid_request" }); - var tokenId = await _tokenService.CreateTokenAsync(request.CvDocumentId, request.Email, request.Language, ct); + var tokenId = await _tokenService.CreateTokenAsync(request.CvDocumentId, request.Email, request.Language, request.Keywords, ct); return Ok(new CreateJobSearchTokenResponse { TokenId = tokenId }); } catch (Exception ex) diff --git a/Apis/cv-matcher-api/Services/Contracts/IJobTokenService.cs b/Apis/cv-matcher-api/Services/Contracts/IJobTokenService.cs index 8f04b35..4f8ba25 100644 --- a/Apis/cv-matcher-api/Services/Contracts/IJobTokenService.cs +++ b/Apis/cv-matcher-api/Services/Contracts/IJobTokenService.cs @@ -12,12 +12,13 @@ public interface IJobTokenService /// Identifier of the indexed CV document. /// Email address of the user who will receive the results. /// Preferred language for result emails (e.g. "en", "ro"). + /// Job search keywords extracted by the LLM during the match call. /// Cancellation token. /// /// The generated token ID to embed in the one-click job search link, /// or null when no job providers are currently enabled (link should be suppressed). /// - Task CreateTokenAsync(string cvDocumentId, string email, string language, CancellationToken ct); + Task CreateTokenAsync(string cvDocumentId, string email, string language, IReadOnlyList keywords, CancellationToken ct); /// /// Validates the token and, if valid, marks it as used and creates a Pending job search session. diff --git a/Apis/cv-matcher-api/Services/JobTokenService.cs b/Apis/cv-matcher-api/Services/JobTokenService.cs index b565071..e2a07d8 100644 --- a/Apis/cv-matcher-api/Services/JobTokenService.cs +++ b/Apis/cv-matcher-api/Services/JobTokenService.cs @@ -1,6 +1,4 @@ using System.Text.Json; -using System.Text.RegularExpressions; -using Api.Clients.Api.Contracts; using Api.Services.Contracts; using CvMatcher.Models.Responses; using CvSearch.Data; @@ -16,28 +14,27 @@ namespace Api.Services; /// Provider configuration is read from cvSearch.JobProviders at session-creation time and /// snapshotted into JobSearchSessionEntity.ProviderConfigJson so subsequent config changes /// do not affect already-queued sessions. +/// Keywords are extracted by the LLM during the CV-to-job match call and stored on the token, +/// then copied to the session when the user clicks the link — no extra RAG call needed. /// public sealed class JobTokenService : IJobTokenService { private readonly CvSearchDbContext _db; - private readonly IRagApiClient _rag; private readonly JobSearchSettings _settings; private readonly ILogger _logger; public JobTokenService( CvSearchDbContext db, - IRagApiClient rag, IOptions settings, ILogger logger) { _db = db; - _rag = rag; _settings = settings.Value; _logger = logger; } /// - public async Task CreateTokenAsync(string cvDocumentId, string email, string language, CancellationToken ct) + public async Task CreateTokenAsync(string cvDocumentId, string email, string language, IReadOnlyList keywords, CancellationToken ct) { var hasEnabledProviders = await _db.JobProviders.AnyAsync(p => p.Enabled, ct); if (!hasEnabledProviders) @@ -52,6 +49,7 @@ public sealed class JobTokenService : IJobTokenService CvDocumentId = cvDocumentId, Email = email, Language = language, + Keywords = string.Join(",", keywords), ExpiresAt = DateTime.UtcNow.AddDays(_settings.TokenExpiryDays), Used = false, CreatedAt = DateTime.UtcNow @@ -59,7 +57,7 @@ public sealed class JobTokenService : IJobTokenService _db.JobSearchTokens.Add(token); await _db.SaveChangesAsync(ct); - _logger.LogInformation("Job search token created. TokenId={TokenId}, CvDocumentId={CvDocumentId}", token.Id, cvDocumentId); + _logger.LogInformation("Job search token created. TokenId={TokenId}, CvDocumentId={CvDocumentId}, Keywords={Keywords}", token.Id, cvDocumentId, token.Keywords); return token.Id; } @@ -74,8 +72,7 @@ public sealed class JobTokenService : IJobTokenService token.Used = true; await _db.SaveChangesAsync(ct); - var cv = await _rag.GetDocumentAsync(token.CvDocumentId, ct); - var keywords = cv is not null ? ExtractKeywords(cv.Text) : string.Empty; + var keywords = token.Keywords; var enabledProviders = await _db.JobProviders .Where(p => p.Enabled) @@ -108,10 +105,6 @@ public sealed class JobTokenService : IJobTokenService return StartJobSearchStatus.Started; } - /// - /// Maps a to the DTO used by - /// cv-search-job. The InitialKeywords list is stored as a JSON array in the entity. - /// private static JobProviderConfig ToConfig(JobProviderEntity entity) { List keywords; @@ -136,27 +129,4 @@ public sealed class JobTokenService : IJobTokenService }; } - /// - /// Extracts up to 10 meaningful keywords from the CV text using simple heuristics (no LLM). - /// Samples the first 2000 characters (where title/role/skills usually appear), splits by - /// whitespace and common delimiters, strips punctuation, and deduplicates. - /// Works regardless of whether the PDF extractor preserves newlines. - /// - private static string ExtractKeywords(string cvText) - { - // Focus on the header area where name/title/skills typically appear - var sample = cvText.Length > 2000 ? cvText[..2000] : cvText; - - var words = sample - .Split([' ', '\n', '\r', '\t', '|', '/', ',', ';', '(', ')'], StringSplitOptions.RemoveEmptyEntries) - .Select(w => Regex.Replace(w, @"[^\w\-]", "").Trim('-')) - .Where(w => w.Length > 2) - .Where(w => !Regex.IsMatch(w, @"^[\d\-]+$")) // skip phone fragments and pure numbers - .Where(w => !w.Contains('@') && !w.Contains('.')) // skip emails and URLs - .Distinct(StringComparer.OrdinalIgnoreCase) - .Take(10) - .ToList(); - - return string.Join(",", words); - } } diff --git a/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.Designer.cs b/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.Designer.cs new file mode 100644 index 0000000..6ed683b --- /dev/null +++ b/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.Designer.cs @@ -0,0 +1,130 @@ +// +using System; +using CvMatcher.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CvMatcher.Data.Migrations +{ + [DbContext(typeof(CvMatcherDbContext))] + [Migration("20260529140000_UpdateCvMatchSystemPromptKeywords")] + partial class UpdateCvMatchSystemPromptKeywords + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("cvMatcher") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CvMatcher.Data.Entities.AiPromptEntity", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Language") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("Description") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Key", "Language"); + + b.ToTable("AiPrompts", "cvMatcher"); + }); + + modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("JobDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Language") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResultJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Score") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CvDocumentId", "JobDocumentId") + .IsUnique(); + + b.ToTable("Results", "cvMatcher"); + }); + + modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCache", "cvMatcher"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.cs b/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.cs new file mode 100644 index 0000000..5134906 --- /dev/null +++ b/Apis/cv-matcher-data/Migrations/20260529140000_UpdateCvMatchSystemPromptKeywords.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using CvMatcher.Data; + +#nullable disable + +namespace CvMatcher.Data.Migrations +{ + /// + public partial class UpdateCvMatchSystemPromptKeywords : Migration + { + private const string OldPrompt = + "You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\n" + + "Penalize missing required skills. Do not invent experience. Use concise business language.\n" + + "Respond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\n" + + "JSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"]}"; + + private const string NewPrompt = + "You are a strict CV-to-job matching engine. Return JSON only. Score realistically from 0 to 100.\n" + + "Penalize missing required skills. Do not invent experience. Use concise business language.\n" + + "Respond entirely in {{languageName}} — all text fields in the JSON must be in {{languageName}}.\n" + + "Also extract 8 to 12 English job search keywords from the CV — job titles, technologies, skills, and domains.\n" + + "The keywords array must always be in English regardless of {{languageName}}. Exclude names, emails, phone numbers, and locations.\n" + + "JSON shape: {\"score\":number,\"summary\":\"...\",\"strengths\":[\"...\"],\"gaps\":[\"...\"],\"recommendations\":[\"...\"],\"evidence\":[\"...\"],\"keywords\":[\"term1\",\"term2\"]}"; + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "AiPrompts", + keyColumns: ["Key", "Language"], + keyValues: new object[] { "ai.cv-match.system-prompt", "*" }, + column: "Value", + value: NewPrompt); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + schema: MigrationConstants.SchemaName, + table: "AiPrompts", + keyColumns: ["Key", "Language"], + keyValues: new object[] { "ai.cv-match.system-prompt", "*" }, + column: "Value", + value: OldPrompt); + } + } +} diff --git a/Apis/cv-search-data/Data/CvSearchDbContext.cs b/Apis/cv-search-data/Data/CvSearchDbContext.cs index 891243d..6bc1133 100644 --- a/Apis/cv-search-data/Data/CvSearchDbContext.cs +++ b/Apis/cv-search-data/Data/CvSearchDbContext.cs @@ -34,6 +34,7 @@ public sealed class CvSearchDbContext : DbContext entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired(); entity.Property(x => x.Email).HasMaxLength(256).IsRequired(); entity.Property(x => x.Language).HasMaxLength(8).HasDefaultValue("en").IsRequired(); + entity.Property(x => x.Keywords).HasMaxLength(1000).HasDefaultValue(string.Empty); entity.Property(x => x.Used).HasDefaultValue(false); entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()"); }); diff --git a/Apis/cv-search-data/Data/Entities/JobSearchTokenEntity.cs b/Apis/cv-search-data/Data/Entities/JobSearchTokenEntity.cs index e3d768c..68bd984 100644 --- a/Apis/cv-search-data/Data/Entities/JobSearchTokenEntity.cs +++ b/Apis/cv-search-data/Data/Entities/JobSearchTokenEntity.cs @@ -9,4 +9,5 @@ public sealed class JobSearchTokenEntity : BaseEntity public string Language { get; set; } = "en"; public DateTime ExpiresAt { get; set; } public bool Used { get; set; } + public string Keywords { get; set; } = string.Empty; } diff --git a/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.Designer.cs b/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.Designer.cs new file mode 100644 index 0000000..d616025 --- /dev/null +++ b/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.Designer.cs @@ -0,0 +1,229 @@ +// +using System; +using CvSearch.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CvSearch.Data.Migrations +{ + [DbContext(typeof(CvSearchDbContext))] + [Migration("20260529130000_AddKeywordsToJobSearchTokens")] + partial class AddKeywordsToJobSearchTokens + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("cvSearch") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CvSearch.Data.Entities.JobProviderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DisplayOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("InitialKeywordsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasDefaultValue("[]"); + + b.Property("JobLinkContains") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("MaxResults") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(20); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("SearchUrlTemplate") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("nvarchar(1024)"); + + b.HasKey("Id"); + + b.ToTable("JobProviders", "cvSearch"); + }); + + modelBuilder.Entity("CvSearch.Data.Entities.JobSearchResultEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("JobText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("JobTitle") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("JobUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ResultJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Score") + .HasColumnType("int"); + + b.Property("SessionId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("Id"); + + b.HasIndex("SessionId"); + + b.ToTable("JobSearchResults", "cvSearch"); + }); + + modelBuilder.Entity("CvSearch.Data.Entities.JobSearchSessionEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Keywords") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Language") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)") + .HasDefaultValue("en"); + + b.Property("ProviderConfigJson") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("TokenId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.HasKey("Id"); + + b.HasIndex("Status"); + + b.ToTable("JobSearchSessions", "cvSearch"); + }); + + modelBuilder.Entity("CvSearch.Data.Entities.JobSearchTokenEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("Keywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasDefaultValue(""); + + b.Property("Language") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(8) + .HasColumnType("nvarchar(8)") + .HasDefaultValue("en"); + + b.Property("Used") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.HasKey("Id"); + + b.ToTable("JobSearchTokens", "cvSearch"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.cs b/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.cs new file mode 100644 index 0000000..f346cd2 --- /dev/null +++ b/Apis/cv-search-data/Migrations/20260529130000_AddKeywordsToJobSearchTokens.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using CvSearch.Data; + +#nullable disable + +namespace CvSearch.Data.Migrations +{ + /// + public partial class AddKeywordsToJobSearchTokens : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Keywords", + schema: MigrationConstants.SchemaName, + table: "JobSearchTokens", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Keywords", + schema: MigrationConstants.SchemaName, + table: "JobSearchTokens"); + } + } +} diff --git a/Apis/cv-search-data/Migrations/CvSearchDbContextModelSnapshot.cs b/Apis/cv-search-data/Migrations/CvSearchDbContextModelSnapshot.cs index 2b7a10c..9005fb5 100644 --- a/Apis/cv-search-data/Migrations/CvSearchDbContextModelSnapshot.cs +++ b/Apis/cv-search-data/Migrations/CvSearchDbContextModelSnapshot.cs @@ -197,6 +197,13 @@ namespace CvSearch.Data.Migrations b.Property("ExpiresAt") .HasColumnType("datetime2"); + b.Property("Keywords") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasDefaultValue(""); + b.Property("Language") .IsRequired() .ValueGeneratedOnAdd()