From 89cb5a10daeefbecee50842e25c7e69cb6c5a1cc Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Wed, 6 May 2026 12:19:31 +0300 Subject: [PATCH] Changes --- api/Program.cs | 4 +- api/appsettings.Development.json | 4 +- api/appsettings.json | 2 +- .../Ai}/Contracts/IMatcherAiClient.cs | 2 +- .../{Services => Clients/Ai}/HashHelper.cs | 2 +- .../Ai}/MatcherAiClient.cs | 7 +- .../Api}/Contracts/IRagApiClient.cs | 6 +- .../Clients/Api/Contracts/IRefitRagApi.cs | 30 +++++++ cv-matcher-api/Clients/Api/RagApiClient.cs | 48 +++++++++++ cv-matcher-api/Controllers/CvController.cs | 2 +- .../Contracts/IMatcherRepository.cs | 4 +- .../Repositories}/EfMatcherRepository.cs | 6 +- cv-matcher-api/Database/schema.sql | 25 ------ .../{ => Models}/Requests/FindJobsRequest.cs | 2 +- .../{ => Models}/Requests/MatchJobRequest.cs | 2 +- .../{ => Models}/Requests/RagSearchRequest.cs | 2 +- .../Responses/CvUploadResponse.cs | 2 +- .../Responses/FindJobsResponse.cs | 2 +- .../Responses/JobMatchResponse.cs | 2 +- .../Responses/RagIndexResponse.cs | 2 +- .../Responses/RagSearchResponse.cs | 2 +- .../{ => Models}/Settings/Settings.cs | 2 +- cv-matcher-api/Program.cs | 27 ++++++- .../Services/Contracts/ICvMatcherService.cs | 4 +- cv-matcher-api/Services/CvMatcherService.cs | 9 ++- cv-matcher-api/Services/EmailService.cs | 2 +- cv-matcher-api/Services/JobTextExtractor.cs | 2 +- cv-matcher-api/Services/RagApiClient.cs | 80 ------------------- cv-matcher-api/appsettings.json | 2 +- cv-matcher-api/cv-matcher-api.csproj | 12 +-- rag-api/Database/schema.sql | 63 --------------- rag-api/Program.cs | 4 +- rag-api/appsettings.json | 2 +- rag-api/rag-api.csproj | 57 +++++++++++-- 34 files changed, 198 insertions(+), 226 deletions(-) rename cv-matcher-api/{Services => Clients/Ai}/Contracts/IMatcherAiClient.cs (82%) rename cv-matcher-api/{Services => Clients/Ai}/HashHelper.cs (91%) rename cv-matcher-api/{Services => Clients/Ai}/MatcherAiClient.cs (97%) rename cv-matcher-api/{Services => Clients/Api}/Contracts/IRagApiClient.cs (81%) create mode 100644 cv-matcher-api/Clients/Api/Contracts/IRefitRagApi.cs create mode 100644 cv-matcher-api/Clients/Api/RagApiClient.cs rename cv-matcher-api/{Services => Data/Repositories}/Contracts/IMatcherRepository.cs (88%) rename cv-matcher-api/{Services => Data/Repositories}/EfMatcherRepository.cs (96%) delete mode 100644 cv-matcher-api/Database/schema.sql rename cv-matcher-api/{ => Models}/Requests/FindJobsRequest.cs (85%) rename cv-matcher-api/{ => Models}/Requests/MatchJobRequest.cs (89%) rename cv-matcher-api/{ => Models}/Requests/RagSearchRequest.cs (87%) rename cv-matcher-api/{ => Models}/Responses/CvUploadResponse.cs (93%) rename cv-matcher-api/{ => Models}/Responses/FindJobsResponse.cs (84%) rename cv-matcher-api/{ => Models}/Responses/JobMatchResponse.cs (93%) rename cv-matcher-api/{ => Models}/Responses/RagIndexResponse.cs (93%) rename cv-matcher-api/{ => Models}/Responses/RagSearchResponse.cs (97%) rename cv-matcher-api/{ => Models}/Settings/Settings.cs (98%) delete mode 100644 cv-matcher-api/Services/RagApiClient.cs delete mode 100644 rag-api/Database/schema.sql diff --git a/api/Program.cs b/api/Program.cs index 493590c..88d1707 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -210,8 +210,8 @@ try logger.LogInformation("Environment: {Environment}", app.Environment.EnvironmentName); // Log all environment variables and configuration settings at startup - // Can be controlled via appsettings: "Logging:LogEnvironmentOnStartup": true - var logEnvironmentOnStartup = app.Configuration.GetValue("Logging:LogEnvironmentOnStartup", defaultValue: true); + // Can be controlled via appsettings: "LogEnvironmentOnStartup": true + var logEnvironmentOnStartup = app.Configuration.GetValue("LogEnvironmentOnStartup", defaultValue: true); if (logEnvironmentOnStartup) { LogEnvironmentSettings(logger, app.Configuration, app.Environment); diff --git a/api/appsettings.Development.json b/api/appsettings.Development.json index f3cde72..b44be4c 100644 --- a/api/appsettings.Development.json +++ b/api/appsettings.Development.json @@ -36,9 +36,9 @@ "Microsoft.AspNetCore.Routing": "Warning", "System.Net.Http.HttpClient": "Warning", "Api": "Debug" - }, - "LogEnvironmentOnStartup": true + } }, + "LogEnvironmentOnStartup": true, "KeyVault": { "VaultUri": "", "Enabled": false diff --git a/api/appsettings.json b/api/appsettings.json index 6fb2ddd..88de872 100644 --- a/api/appsettings.json +++ b/api/appsettings.json @@ -58,7 +58,6 @@ ] }, "Logging": { - "LogEnvironmentOnStartup": true, "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", @@ -68,6 +67,7 @@ "Api": "Information" } }, + "LogEnvironmentOnStartup": true, "AllowedHosts": "*", "KeyVault": { "VaultUri": "", diff --git a/cv-matcher-api/Services/Contracts/IMatcherAiClient.cs b/cv-matcher-api/Clients/Ai/Contracts/IMatcherAiClient.cs similarity index 82% rename from cv-matcher-api/Services/Contracts/IMatcherAiClient.cs rename to cv-matcher-api/Clients/Ai/Contracts/IMatcherAiClient.cs index 2a640b0..95654ed 100644 --- a/cv-matcher-api/Services/Contracts/IMatcherAiClient.cs +++ b/cv-matcher-api/Clients/Ai/Contracts/IMatcherAiClient.cs @@ -1,4 +1,4 @@ -namespace Api.Services.Contracts; +namespace Api.Clients.Ai.Contracts; public interface IMatcherAiClient { diff --git a/cv-matcher-api/Services/HashHelper.cs b/cv-matcher-api/Clients/Ai/HashHelper.cs similarity index 91% rename from cv-matcher-api/Services/HashHelper.cs rename to cv-matcher-api/Clients/Ai/HashHelper.cs index 4081528..33950c6 100644 --- a/cv-matcher-api/Services/HashHelper.cs +++ b/cv-matcher-api/Clients/Ai/HashHelper.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace Api.Services; +namespace Api.Clients.Ai; public static class HashHelper { diff --git a/cv-matcher-api/Services/MatcherAiClient.cs b/cv-matcher-api/Clients/Ai/MatcherAiClient.cs similarity index 97% rename from cv-matcher-api/Services/MatcherAiClient.cs rename to cv-matcher-api/Clients/Ai/MatcherAiClient.cs index 2acdb09..1d2f4a4 100644 --- a/cv-matcher-api/Services/MatcherAiClient.cs +++ b/cv-matcher-api/Clients/Ai/MatcherAiClient.cs @@ -2,11 +2,12 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using Api.Services.Contracts; -using Api.Settings; +using Api.Clients.Ai.Contracts; +using Api.Data.Repositories.Contracts; +using Api.Models.Settings; using Microsoft.Extensions.Options; -namespace Api.Services; +namespace Api.Clients.Ai; public sealed class MatcherAiClient : IMatcherAiClient { diff --git a/cv-matcher-api/Services/Contracts/IRagApiClient.cs b/cv-matcher-api/Clients/Api/Contracts/IRagApiClient.cs similarity index 81% rename from cv-matcher-api/Services/Contracts/IRagApiClient.cs rename to cv-matcher-api/Clients/Api/Contracts/IRagApiClient.cs index 2d823c6..3af9722 100644 --- a/cv-matcher-api/Services/Contracts/IRagApiClient.cs +++ b/cv-matcher-api/Clients/Api/Contracts/IRagApiClient.cs @@ -1,7 +1,7 @@ -using Api.Requests; -using Api.Responses; +using Api.Models.Requests; +using Api.Models.Responses; -namespace Api.Services.Contracts; +namespace Api.Clients.Api.Contracts; public interface IRagApiClient { diff --git a/cv-matcher-api/Clients/Api/Contracts/IRefitRagApi.cs b/cv-matcher-api/Clients/Api/Contracts/IRefitRagApi.cs new file mode 100644 index 0000000..85315c7 --- /dev/null +++ b/cv-matcher-api/Clients/Api/Contracts/IRefitRagApi.cs @@ -0,0 +1,30 @@ +using Refit; +using Api.Models.Responses; +using Api.Models.Requests; + +namespace Api.Clients.Api.Contracts; + +[Headers("Accept: application/json")] +public interface IRefitRagApi +{ + [Multipart] + [Post("/api/rag/documents")] + Task IndexDocumentAsync([AliasAs("file")] StreamPart file, + [AliasAs("documentType")] string documentType, + [AliasAs("title")] string title, + CancellationToken ct = default); + + [Multipart] + [Post("/api/rag/documents")] + Task IndexDocumentWithTextAsync([AliasAs("text")] string text, + [AliasAs("documentType")] string documentType, + [AliasAs("title")] string title, + [AliasAs("sourceUrl")] string? sourceUrl = null, + CancellationToken ct = default); + + [Get("/api/rag/documents/{documentId}")] + Task GetDocumentAsync(string documentId, CancellationToken ct = default); + + [Post("/api/rag/search")] + Task SearchAsync([Body] RagSearchRequest request, CancellationToken ct = default); +} diff --git a/cv-matcher-api/Clients/Api/RagApiClient.cs b/cv-matcher-api/Clients/Api/RagApiClient.cs new file mode 100644 index 0000000..f7f8cfa --- /dev/null +++ b/cv-matcher-api/Clients/Api/RagApiClient.cs @@ -0,0 +1,48 @@ +using System.Net; +using Refit; +using Microsoft.Extensions.Options; +using Api.Clients.Api.Contracts; +using Api.Models.Responses; +using Api.Models.Settings; +using Api.Models.Requests; + +namespace Api.Clients.Api; + +public sealed class RagApiClient : IRagApiClient +{ + private readonly IRefitRagApi _refit; + + public RagApiClient(IRefitRagApi refit, IOptions options) + { + _refit = refit; + } + + public async Task IndexCvPdfAsync(IFormFile file, CancellationToken ct) + { + await using var stream = file.OpenReadStream(); + var part = new StreamPart(stream, file.FileName, "application/pdf"); + return await _refit.IndexDocumentAsync(part, "cv", file.FileName, ct); + } + + public async Task IndexJobTextAsync(string text, string? url, string? title, CancellationToken ct) + { + return await _refit.IndexDocumentWithTextAsync(text, "job", title ?? "Job description", url, ct); + } + + public async Task GetDocumentAsync(string documentId, CancellationToken ct) + { + try + { + return await _refit.GetDocumentAsync(documentId, ct); + } + catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + return null; + } + } + + public async Task SearchAsync(RagSearchRequest request, CancellationToken ct) + { + return await _refit.SearchAsync(request, ct); + } +} diff --git a/cv-matcher-api/Controllers/CvController.cs b/cv-matcher-api/Controllers/CvController.cs index 3f0a382..9a6da9e 100644 --- a/cv-matcher-api/Controllers/CvController.cs +++ b/cv-matcher-api/Controllers/CvController.cs @@ -1,4 +1,4 @@ -using Api.Requests; +using Api.Models.Requests; using Api.Services.Contracts; using Microsoft.AspNetCore.Mvc; diff --git a/cv-matcher-api/Services/Contracts/IMatcherRepository.cs b/cv-matcher-api/Data/Repositories/Contracts/IMatcherRepository.cs similarity index 88% rename from cv-matcher-api/Services/Contracts/IMatcherRepository.cs rename to cv-matcher-api/Data/Repositories/Contracts/IMatcherRepository.cs index 6fa427e..628921f 100644 --- a/cv-matcher-api/Services/Contracts/IMatcherRepository.cs +++ b/cv-matcher-api/Data/Repositories/Contracts/IMatcherRepository.cs @@ -1,6 +1,6 @@ -using Api.Responses; +using Api.Models.Responses; -namespace Api.Services.Contracts; +namespace Api.Data.Repositories.Contracts; public interface IMatcherRepository { diff --git a/cv-matcher-api/Services/EfMatcherRepository.cs b/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs similarity index 96% rename from cv-matcher-api/Services/EfMatcherRepository.cs rename to cv-matcher-api/Data/Repositories/EfMatcherRepository.cs index 0e84095..4c3c1ac 100644 --- a/cv-matcher-api/Services/EfMatcherRepository.cs +++ b/cv-matcher-api/Data/Repositories/EfMatcherRepository.cs @@ -1,11 +1,11 @@ using System.Text.Json; using Api.Data; using Api.Data.Entities; -using Api.Responses; -using Api.Services.Contracts; +using Api.Data.Repositories.Contracts; +using Api.Models.Responses; using Microsoft.EntityFrameworkCore; -namespace Api.Services; +namespace Api.Data.Repositories; public sealed class EfMatcherRepository : IMatcherRepository { diff --git a/cv-matcher-api/Database/schema.sql b/cv-matcher-api/Database/schema.sql deleted file mode 100644 index 05c96a4..0000000 --- a/cv-matcher-api/Database/schema.sql +++ /dev/null @@ -1,25 +0,0 @@ -IF OBJECT_ID('dbo.CvMatchResults', 'U') IS NULL -BEGIN - CREATE TABLE dbo.CvMatchResults ( - Id NVARCHAR(64) NOT NULL CONSTRAINT PK_CvMatchResults PRIMARY KEY, - CvDocumentId NVARCHAR(64) NOT NULL, - JobDocumentId NVARCHAR(64) NOT NULL, - ResultJson NVARCHAR(MAX) NOT NULL, - Score INT NOT NULL, - CreatedAt DATETIME2 NOT NULL CONSTRAINT DF_CvMatchResults_CreatedAt DEFAULT SYSUTCDATETIME() - ); - CREATE UNIQUE INDEX UX_CvMatchResults_CvJob ON dbo.CvMatchResults(CvDocumentId, JobDocumentId); -END -GO - -IF OBJECT_ID('dbo.CvMatcherChatCache', 'U') IS NULL -BEGIN - CREATE TABLE dbo.CvMatcherChatCache ( - CacheKey NVARCHAR(64) NOT NULL CONSTRAINT PK_CvMatcherChatCache PRIMARY KEY, - Model NVARCHAR(120) NOT NULL, - Temperature DECIMAL(4,2) NOT NULL, - ResponseText NVARCHAR(MAX) NOT NULL, - CreatedAt DATETIME2 NOT NULL CONSTRAINT DF_CvMatcherChatCache_CreatedAt DEFAULT SYSUTCDATETIME() - ); -END -GO diff --git a/cv-matcher-api/Requests/FindJobsRequest.cs b/cv-matcher-api/Models/Requests/FindJobsRequest.cs similarity index 85% rename from cv-matcher-api/Requests/FindJobsRequest.cs rename to cv-matcher-api/Models/Requests/FindJobsRequest.cs index ef6c742..42f702c 100644 --- a/cv-matcher-api/Requests/FindJobsRequest.cs +++ b/cv-matcher-api/Models/Requests/FindJobsRequest.cs @@ -1,4 +1,4 @@ -namespace Api.Requests +namespace Api.Models.Requests { public sealed class FindJobsRequest { diff --git a/cv-matcher-api/Requests/MatchJobRequest.cs b/cv-matcher-api/Models/Requests/MatchJobRequest.cs similarity index 89% rename from cv-matcher-api/Requests/MatchJobRequest.cs rename to cv-matcher-api/Models/Requests/MatchJobRequest.cs index 429ae16..02b16bd 100644 --- a/cv-matcher-api/Requests/MatchJobRequest.cs +++ b/cv-matcher-api/Models/Requests/MatchJobRequest.cs @@ -1,4 +1,4 @@ -namespace Api.Requests +namespace Api.Models.Requests { public sealed class MatchJobRequest { diff --git a/cv-matcher-api/Requests/RagSearchRequest.cs b/cv-matcher-api/Models/Requests/RagSearchRequest.cs similarity index 87% rename from cv-matcher-api/Requests/RagSearchRequest.cs rename to cv-matcher-api/Models/Requests/RagSearchRequest.cs index 04a1089..9375590 100644 --- a/cv-matcher-api/Requests/RagSearchRequest.cs +++ b/cv-matcher-api/Models/Requests/RagSearchRequest.cs @@ -1,4 +1,4 @@ -namespace Api.Requests +namespace Api.Models.Requests { public sealed class RagSearchRequest { diff --git a/cv-matcher-api/Responses/CvUploadResponse.cs b/cv-matcher-api/Models/Responses/CvUploadResponse.cs similarity index 93% rename from cv-matcher-api/Responses/CvUploadResponse.cs rename to cv-matcher-api/Models/Responses/CvUploadResponse.cs index 726a9e0..f6daff8 100644 --- a/cv-matcher-api/Responses/CvUploadResponse.cs +++ b/cv-matcher-api/Models/Responses/CvUploadResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Responses +namespace Api.Models.Responses { public sealed class CvUploadResponse { diff --git a/cv-matcher-api/Responses/FindJobsResponse.cs b/cv-matcher-api/Models/Responses/FindJobsResponse.cs similarity index 84% rename from cv-matcher-api/Responses/FindJobsResponse.cs rename to cv-matcher-api/Models/Responses/FindJobsResponse.cs index 0adc942..8f95ce8 100644 --- a/cv-matcher-api/Responses/FindJobsResponse.cs +++ b/cv-matcher-api/Models/Responses/FindJobsResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Responses +namespace Api.Models.Responses { public sealed class FindJobsResponse { diff --git a/cv-matcher-api/Responses/JobMatchResponse.cs b/cv-matcher-api/Models/Responses/JobMatchResponse.cs similarity index 93% rename from cv-matcher-api/Responses/JobMatchResponse.cs rename to cv-matcher-api/Models/Responses/JobMatchResponse.cs index 2f54ac3..6a04cf5 100644 --- a/cv-matcher-api/Responses/JobMatchResponse.cs +++ b/cv-matcher-api/Models/Responses/JobMatchResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Responses +namespace Api.Models.Responses { public sealed class JobMatchResponse { diff --git a/cv-matcher-api/Responses/RagIndexResponse.cs b/cv-matcher-api/Models/Responses/RagIndexResponse.cs similarity index 93% rename from cv-matcher-api/Responses/RagIndexResponse.cs rename to cv-matcher-api/Models/Responses/RagIndexResponse.cs index a62eb69..ce108e4 100644 --- a/cv-matcher-api/Responses/RagIndexResponse.cs +++ b/cv-matcher-api/Models/Responses/RagIndexResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Responses +namespace Api.Models.Responses { public sealed class RagIndexResponse { diff --git a/cv-matcher-api/Responses/RagSearchResponse.cs b/cv-matcher-api/Models/Responses/RagSearchResponse.cs similarity index 97% rename from cv-matcher-api/Responses/RagSearchResponse.cs rename to cv-matcher-api/Models/Responses/RagSearchResponse.cs index 9a72539..0a07add 100644 --- a/cv-matcher-api/Responses/RagSearchResponse.cs +++ b/cv-matcher-api/Models/Responses/RagSearchResponse.cs @@ -1,4 +1,4 @@ -namespace Api.Responses +namespace Api.Models.Responses { public sealed class RagSearchResponse { diff --git a/cv-matcher-api/Settings/Settings.cs b/cv-matcher-api/Models/Settings/Settings.cs similarity index 98% rename from cv-matcher-api/Settings/Settings.cs rename to cv-matcher-api/Models/Settings/Settings.cs index b646204..d5e414d 100644 --- a/cv-matcher-api/Settings/Settings.cs +++ b/cv-matcher-api/Models/Settings/Settings.cs @@ -1,4 +1,4 @@ -namespace Api.Settings; +namespace Api.Models.Settings; public sealed class RagApiSettings { diff --git a/cv-matcher-api/Program.cs b/cv-matcher-api/Program.cs index 151984d..1227144 100644 --- a/cv-matcher-api/Program.cs +++ b/cv-matcher-api/Program.cs @@ -2,11 +2,18 @@ using Azure.Identity; using Api.Data; using Api.Services; using Api.Services.Contracts; -using Api.Settings; using Microsoft.AspNetCore.Diagnostics; using Serilog; using System.Reflection; using Microsoft.EntityFrameworkCore; +using Refit; +using Api.Data.Repositories; +using Api.Data.Repositories.Contracts; +using Api.Clients.Api; +using Api.Clients.Api.Contracts; +using Api.Clients.Ai; +using Api.Clients.Ai.Contracts; +using Api.Models.Settings; DotNetEnv.Env.Load(); @@ -68,7 +75,19 @@ try builder.Services.Configure(builder.Configuration.GetSection("Matcher")); builder.Services.Configure(builder.Configuration.GetSection("Smtp")); - builder.Services.AddHttpClient(); + // Register Refit client for the external RAG API and a thin wrapper that implements IRagApiClient + builder.Services.AddRefitClient() + .ConfigureHttpClient((sp, c) => + { + var settings = sp.GetRequiredService>().Value; + c.BaseAddress = new Uri(settings.BaseUrl.TrimEnd('/') + "/"); + if (!string.IsNullOrWhiteSpace(settings.InternalApiKey)) + { + c.DefaultRequestHeaders.Add("X-Internal-Api-Key", settings.InternalApiKey); + } + }); + + builder.Services.AddScoped(); builder.Services.AddHttpClient(); builder.Services.AddHttpClient(); builder.Services.AddDbContext(options => @@ -89,8 +108,8 @@ try logger.LogInformation("Environment: {Environment}", app.Environment.EnvironmentName); // Log all environment variables and configuration settings at startup - // Can be controlled via appsettings: "Logging:LogEnvironmentOnStartup": true - var logEnvironmentOnStartup = app.Configuration.GetValue("Logging:LogEnvironmentOnStartup", defaultValue: true); + // Can be controlled via appsettings: "LogEnvironmentOnStartup": true + var logEnvironmentOnStartup = app.Configuration.GetValue("LogEnvironmentOnStartup", defaultValue: true); if (logEnvironmentOnStartup) { LogEnvironmentSettings(logger, app.Configuration, app.Environment); diff --git a/cv-matcher-api/Services/Contracts/ICvMatcherService.cs b/cv-matcher-api/Services/Contracts/ICvMatcherService.cs index b1c4f17..7f85bb2 100644 --- a/cv-matcher-api/Services/Contracts/ICvMatcherService.cs +++ b/cv-matcher-api/Services/Contracts/ICvMatcherService.cs @@ -1,5 +1,5 @@ -using Api.Requests; -using Api.Responses; +using Api.Models.Requests; +using Api.Models.Responses; namespace Api.Services.Contracts; diff --git a/cv-matcher-api/Services/CvMatcherService.cs b/cv-matcher-api/Services/CvMatcherService.cs index 440670e..4a50bc1 100644 --- a/cv-matcher-api/Services/CvMatcherService.cs +++ b/cv-matcher-api/Services/CvMatcherService.cs @@ -1,8 +1,11 @@ using System.Text.Json; -using Api.Requests; -using Api.Responses; +using Api.Clients.Ai.Contracts; +using Api.Clients.Api.Contracts; +using Api.Data.Repositories.Contracts; +using Api.Models.Requests; +using Api.Models.Responses; +using Api.Models.Settings; using Api.Services.Contracts; -using Api.Settings; using Microsoft.Extensions.Options; namespace Api.Services; diff --git a/cv-matcher-api/Services/EmailService.cs b/cv-matcher-api/Services/EmailService.cs index cc5eb51..f3f1e29 100644 --- a/cv-matcher-api/Services/EmailService.cs +++ b/cv-matcher-api/Services/EmailService.cs @@ -1,5 +1,5 @@ +using Api.Models.Settings; using Api.Services.Contracts; -using Api.Settings; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Options; diff --git a/cv-matcher-api/Services/JobTextExtractor.cs b/cv-matcher-api/Services/JobTextExtractor.cs index 6d63616..7f476b5 100644 --- a/cv-matcher-api/Services/JobTextExtractor.cs +++ b/cv-matcher-api/Services/JobTextExtractor.cs @@ -1,7 +1,7 @@ using System.Net; using System.Text.RegularExpressions; +using Api.Models.Settings; using Api.Services.Contracts; -using Api.Settings; using Microsoft.Extensions.Options; namespace Api.Services; diff --git a/cv-matcher-api/Services/RagApiClient.cs b/cv-matcher-api/Services/RagApiClient.cs deleted file mode 100644 index b63a228..0000000 --- a/cv-matcher-api/Services/RagApiClient.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; -using Api.Requests; -using Api.Responses; -using Api.Services.Contracts; -using Api.Settings; -using Microsoft.Extensions.Options; - -namespace Api.Services; - -public sealed class RagApiClient : IRagApiClient -{ - private readonly HttpClient _http; - private readonly RagApiSettings _settings; - private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); - - public RagApiClient(HttpClient http, IOptions options) - { - _http = http; - _settings = options.Value; - _http.BaseAddress = new Uri(_settings.BaseUrl.TrimEnd('/') + "/"); - if (!string.IsNullOrWhiteSpace(_settings.InternalApiKey)) - { - _http.DefaultRequestHeaders.Add("X-Internal-Api-Key", _settings.InternalApiKey); - } - } - - public async Task IndexCvPdfAsync(IFormFile file, CancellationToken ct) - { - using var content = new MultipartFormDataContent(); - await using var stream = file.OpenReadStream(); - using var fileContent = new StreamContent(stream); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); - content.Add(fileContent, "file", file.FileName); - content.Add(new StringContent("cv"), "documentType"); - content.Add(new StringContent(file.FileName), "title"); - using var response = await _http.PostAsync("api/rag/documents", content, ct); - return await ReadJsonAsync(response, ct); - } - - public async Task IndexJobTextAsync(string text, string? url, string? title, CancellationToken ct) - { - using var content = new MultipartFormDataContent - { - { new StringContent(text), "text" }, - { new StringContent("job"), "documentType" }, - { new StringContent(title ?? "Job description"), "title" } - }; - if (!string.IsNullOrWhiteSpace(url)) content.Add(new StringContent(url), "sourceUrl"); - using var response = await _http.PostAsync("api/rag/documents", content, ct); - return await ReadJsonAsync(response, ct); - } - - public async Task GetDocumentAsync(string documentId, CancellationToken ct) - { - using var response = await _http.GetAsync($"api/rag/documents/{Uri.EscapeDataString(documentId)}", ct); - if (response.StatusCode == System.Net.HttpStatusCode.NotFound) return null; - return await ReadJsonAsync(response, ct); - } - - public async Task SearchAsync(RagSearchRequest request, CancellationToken ct) - { - using var response = await _http.PostAsync( - "api/rag/search", - new StringContent(JsonSerializer.Serialize(request, JsonOptions), Encoding.UTF8, "application/json"), - ct); - return await ReadJsonAsync(response, ct); - } - - private static async Task ReadJsonAsync(HttpResponseMessage response, CancellationToken ct) - { - var json = await response.Content.ReadAsStringAsync(ct); - if (!response.IsSuccessStatusCode) - { - throw new InvalidOperationException($"RAG API failed: {(int)response.StatusCode} {json}"); - } - return JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidOperationException("RAG API returned invalid JSON."); - } -} diff --git a/cv-matcher-api/appsettings.json b/cv-matcher-api/appsettings.json index 1c4fc29..12bea33 100644 --- a/cv-matcher-api/appsettings.json +++ b/cv-matcher-api/appsettings.json @@ -58,7 +58,6 @@ ] }, "Logging": { - "LogEnvironmentOnStartup": true, "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", @@ -68,6 +67,7 @@ "Api": "Information" } }, + "LogEnvironmentOnStartup": true, "AllowedHosts": "*", "KeyVault": { "VaultUri": "", diff --git a/cv-matcher-api/cv-matcher-api.csproj b/cv-matcher-api/cv-matcher-api.csproj index b586214..774b8b3 100644 --- a/cv-matcher-api/cv-matcher-api.csproj +++ b/cv-matcher-api/cv-matcher-api.csproj @@ -1,4 +1,4 @@ - + net10.0 enable @@ -59,8 +59,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -70,10 +70,6 @@ - - - - PreserveNewest - + diff --git a/rag-api/Database/schema.sql b/rag-api/Database/schema.sql deleted file mode 100644 index 53d5d16..0000000 --- a/rag-api/Database/schema.sql +++ /dev/null @@ -1,63 +0,0 @@ -IF OBJECT_ID('dbo.RagChunks', 'U') IS NULL -BEGIN - CREATE TABLE dbo.RagChunks ( - Id NVARCHAR(64) NOT NULL CONSTRAINT PK_RagChunks PRIMARY KEY, - DocumentId NVARCHAR(64) NOT NULL, - ChunkIndex INT NOT NULL, - Text NVARCHAR(MAX) NOT NULL, - Embedding VARBINARY(MAX) NOT NULL - ); -END -GO - -IF OBJECT_ID('dbo.RagDocuments', 'U') IS NULL -BEGIN - CREATE TABLE dbo.RagDocuments ( - Id NVARCHAR(64) NOT NULL CONSTRAINT PK_RagDocuments PRIMARY KEY, - DocumentType NVARCHAR(80) NOT NULL, - Title NVARCHAR(300) NOT NULL, - SourceUrl NVARCHAR(1200) NULL, - RawText NVARCHAR(MAX) NOT NULL, - TextHash NVARCHAR(64) NOT NULL, - TypeConfidence FLOAT NOT NULL, - MetadataJson NVARCHAR(MAX) NOT NULL CONSTRAINT DF_RagDocuments_MetadataJson DEFAULT '{}', - CreatedAt DATETIME2 NOT NULL CONSTRAINT DF_RagDocuments_CreatedAt DEFAULT SYSUTCDATETIME() - ); - - CREATE INDEX IX_RagDocuments_TextHash ON dbo.RagDocuments(TextHash); - CREATE INDEX IX_RagDocuments_DocumentType ON dbo.RagDocuments(DocumentType); -END -GO - -IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_RagChunks_RagDocuments') -BEGIN - ALTER TABLE dbo.RagChunks - ADD CONSTRAINT FK_RagChunks_RagDocuments FOREIGN KEY (DocumentId) REFERENCES dbo.RagDocuments(Id) ON DELETE CASCADE; -END -GO - -IF OBJECT_ID('dbo.RagEmbeddingCache', 'U') IS NULL -BEGIN - CREATE TABLE dbo.RagEmbeddingCache ( - CacheKey NVARCHAR(64) NOT NULL CONSTRAINT PK_RagEmbeddingCache PRIMARY KEY, - Model NVARCHAR(120) NOT NULL, - TextHash NVARCHAR(64) NOT NULL, - Vector VARBINARY(MAX) NOT NULL, - CreatedAt DATETIME2 NOT NULL CONSTRAINT DF_RagEmbeddingCache_CreatedAt DEFAULT SYSUTCDATETIME() - ); - - CREATE INDEX IX_RagEmbeddingCache_TextHash ON dbo.RagEmbeddingCache(TextHash); -END -GO - -IF OBJECT_ID('dbo.RagChatCompletionCache', 'U') IS NULL -BEGIN - CREATE TABLE dbo.RagChatCompletionCache ( - CacheKey NVARCHAR(64) NOT NULL CONSTRAINT PK_RagChatCompletionCache PRIMARY KEY, - Model NVARCHAR(120) NOT NULL, - Temperature DECIMAL(4,2) NOT NULL, - ResponseText NVARCHAR(MAX) NOT NULL, - CreatedAt DATETIME2 NOT NULL CONSTRAINT DF_RagChatCompletionCache_CreatedAt DEFAULT SYSUTCDATETIME() - ); -END -GO diff --git a/rag-api/Program.cs b/rag-api/Program.cs index 5613c81..7340a28 100644 --- a/rag-api/Program.cs +++ b/rag-api/Program.cs @@ -89,8 +89,8 @@ try logger.LogInformation("Environment: {Environment}", app.Environment.EnvironmentName); // Log all environment variables and configuration settings at startup - // Can be controlled via appsettings: "Logging:LogEnvironmentOnStartup": true - var logEnvironmentOnStartup = app.Configuration.GetValue("Logging:LogEnvironmentOnStartup", defaultValue: true); + // Can be controlled via appsettings: "LogEnvironmentOnStartup": true + var logEnvironmentOnStartup = app.Configuration.GetValue("LogEnvironmentOnStartup", defaultValue: true); if (logEnvironmentOnStartup) { LogEnvironmentSettings(logger, app.Configuration, app.Environment); diff --git a/rag-api/appsettings.json b/rag-api/appsettings.json index 2f0c083..c78258a 100644 --- a/rag-api/appsettings.json +++ b/rag-api/appsettings.json @@ -58,7 +58,6 @@ ] }, "Logging": { - "LogEnvironmentOnStartup": true, "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", @@ -68,6 +67,7 @@ "Api": "Information" } }, + "LogEnvironmentOnStartup": true, "AllowedHosts": "*", "KeyVault": { "VaultUri": "", diff --git a/rag-api/rag-api.csproj b/rag-api/rag-api.csproj index 7485e85..f9f5822 100644 --- a/rag-api/rag-api.csproj +++ b/rag-api/rag-api.csproj @@ -6,13 +6,61 @@ Linux Api + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,9 +71,4 @@ - - - PreserveNewest - -