From cd0e32513fe0fea185bce73b1e339d784197874d Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Wed, 6 May 2026 13:59:59 +0300 Subject: [PATCH] Changes --- api/Clients/Api/Contracts/ICvMatcherApi.cs | 15 +++++ api/Controllers/CvMatcherController.cs | 66 ++++++++++------------ api/Program.cs | 28 ++++++++- api/api.csproj | 4 ++ cv-matcher-api/Program.cs | 10 +++- cv-matcher-api/cv-matcher-api.csproj | 3 + rag-api/Program.cs | 8 ++- rag-api/rag-api.csproj | 4 ++ 8 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 api/Clients/Api/Contracts/ICvMatcherApi.cs diff --git a/api/Clients/Api/Contracts/ICvMatcherApi.cs b/api/Clients/Api/Contracts/ICvMatcherApi.cs new file mode 100644 index 0000000..ee95e0f --- /dev/null +++ b/api/Clients/Api/Contracts/ICvMatcherApi.cs @@ -0,0 +1,15 @@ +using System.Net.Http; +using Refit; +using Api.Models.Requests; + +namespace Api.Clients.Api.Contracts; + +public interface ICvMatcherApi +{ + [Multipart] + [Post("/api/cv/upload")] + Task Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent); + + [Post("/api/cv/match-job")] + Task MatchJob([Body] JobMatchRequest request); +} diff --git a/api/Controllers/CvMatcherController.cs b/api/Controllers/CvMatcherController.cs index f8127ea..0e2b000 100644 --- a/api/Controllers/CvMatcherController.cs +++ b/api/Controllers/CvMatcherController.cs @@ -1,36 +1,47 @@ using Api.Models.Requests; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers; +/// +/// Proxy endpoints for the CV matcher API. These endpoints forward requests to the internal cv-matcher-api. +/// [ApiController] [Route("api/cv-matcher")] [EnableRateLimiting("cv-matcher")] public sealed class RagController : ControllerBase { - private readonly IHttpClientFactory _httpClientFactory; + private readonly Api.Clients.Api.Contracts.ICvMatcherApi _cvApi; private readonly IConfiguration _configuration; private readonly ILogger _logger; public RagController( - IHttpClientFactory httpClientFactory, + Api.Clients.Api.Contracts.ICvMatcherApi cvApi, IConfiguration configuration, ILogger logger) { - _httpClientFactory = httpClientFactory; + _cvApi = cvApi; _configuration = configuration; _logger = logger; } + /// + /// Upload a CV PDF to the cv-matcher-api. + /// + /// The uploaded CV PDF file. + /// Whether the user consented to GDPR processing. + /// Cancellation token. [HttpPost("upload")] [RequestSizeLimit(8 * 1024 * 1024)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status502BadGateway)] + [SwaggerOperation(Summary = "Upload CV", Description = "Proxy upload of a CV PDF to the internal cv-matcher-api.")] + [SwaggerResponse(StatusCodes.Status200OK, "Upload succeeded")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Missing or invalid input")] + [SwaggerResponse(StatusCodes.Status502BadGateway, "Upstream cv-matcher-api error")] public async Task UploadCv( [FromForm(Name = "cv")] IFormFile? cv, [FromForm] bool gdprConsent, @@ -53,15 +64,9 @@ public sealed class RagController : ControllerBase _logger.LogInformation("Proxying CV upload to cv-matcher-api. FileName={FileName}, Size={SizeBytes}, GdprConsent={GdprConsent}", cv.FileName, cv.Length, gdprConsent); - using var client = CreateCvMatcherClient(baseUrl); - using var form = new MultipartFormDataContent(); - await using var stream = cv.OpenReadStream(); - using var fileContent = new StreamContent(stream); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); - form.Add(fileContent, "cv", cv.FileName); - form.Add(new StringContent(gdprConsent.ToString().ToLowerInvariant()), "gdprConsent"); - - using var response = await client.PostAsync("api/cv/upload", form, ct); + var stream = cv.OpenReadStream(); + var part = new Refit.StreamPart(stream, cv.FileName, "application/pdf"); + using var response = await _cvApi.Upload(part, gdprConsent); return await ProxyResponseAsync(response, ct); } catch (OperationCanceledException) when (ct.IsCancellationRequested) @@ -76,10 +81,19 @@ public sealed class RagController : ControllerBase } } + /// + /// Proxy a job matching request to the cv-matcher-api. + /// + /// Job match request payload containing CV document id or job description/url. + /// Cancellation token. [HttpPost("match-job")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status502BadGateway)] + [SwaggerOperation(Summary = "Match job", Description = "Proxy job matching request to the internal cv-matcher-api.")] + [SwaggerResponse(StatusCodes.Status200OK, "Match succeeded")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request")] + [SwaggerResponse(StatusCodes.Status502BadGateway, "Upstream cv-matcher-api error")] public async Task MatchJob([FromBody] JobMatchRequest request, CancellationToken ct) { var baseUrl = GetCvMatcherBaseUrl(); @@ -96,13 +110,7 @@ public sealed class RagController : ControllerBase !string.IsNullOrWhiteSpace(request.JobUrl), !string.IsNullOrWhiteSpace(request.JobDescription)); - using var client = CreateCvMatcherClient(baseUrl); - var json = JsonSerializer.Serialize(request, new JsonSerializerOptions(JsonSerializerDefaults.Web)); - using var response = await client.PostAsync( - "api/cv/match-job", - new StringContent(json, Encoding.UTF8, "application/json"), - ct); - + using var response = await _cvApi.MatchJob(request); return await ProxyResponseAsync(response, ct); } catch (OperationCanceledException) when (ct.IsCancellationRequested) @@ -119,19 +127,7 @@ public sealed class RagController : ControllerBase private string GetCvMatcherBaseUrl() => _configuration["CvMatcherApi:BaseUrl"] ?? string.Empty; - private HttpClient CreateCvMatcherClient(string baseUrl) - { - var client = _httpClientFactory.CreateClient("CvMatcherApi"); - client.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); - - var key = _configuration["CvMatcherApi:InternalApiKey"]; - if (!string.IsNullOrWhiteSpace(key) && !client.DefaultRequestHeaders.Contains("X-Internal-Api-Key")) - { - client.DefaultRequestHeaders.Add("X-Internal-Api-Key", key); - } - - return client; - } + // Refit client is configured in Program.cs; this helper only reads config for diagnostics private static async Task ProxyResponseAsync(HttpResponseMessage response, CancellationToken ct) { diff --git a/api/Program.cs b/api/Program.cs index 32c3678..6d4bb07 100644 --- a/api/Program.cs +++ b/api/Program.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Serilog; using System.Reflection; using System.Threading.RateLimiting; +using Refit; // Load .env file if it exists (for local development) @@ -80,11 +81,34 @@ try builder.Services.AddHttpClient(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddHttpClient("CvMatcherApi"); + + // Refit client for CvMatcher API + builder.Services.AddRefitClient() + .ConfigureHttpClient((sp, client) => + { + var config = sp.GetRequiredService(); + var baseUrl = config["CvMatcherApi:BaseUrl"] ?? string.Empty; + if (!string.IsNullOrWhiteSpace(baseUrl)) client.BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/"); + + var key = config["CvMatcherApi:InternalApiKey"]; + if (!string.IsNullOrWhiteSpace(key) && !client.DefaultRequestHeaders.Contains("X-Internal-Api-Key")) + { + client.DefaultRequestHeaders.Add("X-Internal-Api-Key", key); + } + }); // Swagger builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + builder.Services.AddSwaggerGen(options => + { + // Include XML comments (enable in csproj) + var xmlFile = (Assembly.GetExecutingAssembly().GetName().Name ?? "Api") + ".xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (File.Exists(xmlPath)) options.IncludeXmlComments(xmlPath); + + // Enable annotations like [SwaggerOperation], [SwaggerResponse] + options.EnableAnnotations(); + }); // If you're behind Caddy / reverse proxy builder.Services.Configure(options => diff --git a/api/api.csproj b/api/api.csproj index 3e988b6..db90fd6 100644 --- a/api/api.csproj +++ b/api/api.csproj @@ -11,6 +11,8 @@ Linux true Api + true + $(NoWarn);1591 @@ -24,6 +26,8 @@ + + diff --git a/cv-matcher-api/Program.cs b/cv-matcher-api/Program.cs index 1227144..c5fda2b 100644 --- a/cv-matcher-api/Program.cs +++ b/cv-matcher-api/Program.cs @@ -7,6 +7,8 @@ using Serilog; using System.Reflection; using Microsoft.EntityFrameworkCore; using Refit; +using System.IO; +using Swashbuckle.AspNetCore.Annotations; using Api.Data.Repositories; using Api.Data.Repositories.Contracts; using Api.Clients.Api; @@ -99,7 +101,13 @@ try builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + builder.Services.AddSwaggerGen(options => + { + var xmlFile = (Assembly.GetExecutingAssembly().GetName().Name ?? "cv-matcher-api") + ".xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (File.Exists(xmlPath)) options.IncludeXmlComments(xmlPath); + options.EnableAnnotations(); + }); var app = builder.Build(); diff --git a/cv-matcher-api/cv-matcher-api.csproj b/cv-matcher-api/cv-matcher-api.csproj index 774b8b3..9a7ab2c 100644 --- a/cv-matcher-api/cv-matcher-api.csproj +++ b/cv-matcher-api/cv-matcher-api.csproj @@ -5,6 +5,8 @@ enable Linux Api + true + $(NoWarn);1591 @@ -70,6 +72,7 @@ + diff --git a/rag-api/Program.cs b/rag-api/Program.cs index 33ba0a9..009458f 100644 --- a/rag-api/Program.cs +++ b/rag-api/Program.cs @@ -84,7 +84,13 @@ try builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + builder.Services.AddSwaggerGen(options => + { + var xmlFile = (Assembly.GetExecutingAssembly().GetName().Name ?? "rag-api") + ".xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + if (File.Exists(xmlPath)) options.IncludeXmlComments(xmlPath); + options.EnableAnnotations(); + }); var app = builder.Build(); diff --git a/rag-api/rag-api.csproj b/rag-api/rag-api.csproj index f9f5822..9355f7b 100644 --- a/rag-api/rag-api.csproj +++ b/rag-api/rag-api.csproj @@ -5,6 +5,8 @@ enable Linux Api + true + $(NoWarn);1591 @@ -70,5 +72,7 @@ + +