147 lines
5.8 KiB
C#
147 lines
5.8 KiB
C#
using Api.Requests;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.RateLimiting;
|
|
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/cv-matcher")]
|
|
[EnableRateLimiting("cv-matcher")]
|
|
public sealed class RagController : ControllerBase
|
|
{
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILogger<RagController> _logger;
|
|
|
|
public RagController(
|
|
IHttpClientFactory httpClientFactory,
|
|
IConfiguration configuration,
|
|
ILogger<RagController> logger)
|
|
{
|
|
_httpClientFactory = httpClientFactory;
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpPost("upload")]
|
|
[RequestSizeLimit(8 * 1024 * 1024)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> UploadCv(
|
|
[FromForm(Name = "cv")] IFormFile? cv,
|
|
[FromForm] bool gdprConsent,
|
|
CancellationToken ct)
|
|
{
|
|
if (cv is null)
|
|
{
|
|
return BadRequest(new { error = "Missing CV PDF." });
|
|
}
|
|
|
|
var baseUrl = GetCvMatcherBaseUrl();
|
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
{
|
|
_logger.LogError("CvMatcherApi:BaseUrl is not configured. The public API cannot proxy CV upload requests.");
|
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API is not configured." });
|
|
}
|
|
|
|
try
|
|
{
|
|
_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);
|
|
return await ProxyResponseAsync(response, ct);
|
|
}
|
|
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
|
{
|
|
_logger.LogWarning("CV upload proxy request was cancelled by the client.");
|
|
return StatusCode(499, new { error = "Request cancelled." });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "CV upload proxy request failed.");
|
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
|
|
}
|
|
}
|
|
|
|
[HttpPost("match-job")]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status502BadGateway)]
|
|
public async Task<IActionResult> MatchJob([FromBody] JobMatchRequest request, CancellationToken ct)
|
|
{
|
|
var baseUrl = GetCvMatcherBaseUrl();
|
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
{
|
|
_logger.LogError("CvMatcherApi:BaseUrl is not configured. The public API cannot proxy job matching requests.");
|
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API is not configured." });
|
|
}
|
|
|
|
try
|
|
{
|
|
_logger.LogInformation("Proxying job match request to cv-matcher-api. CvDocumentId={CvDocumentId}, HasJobUrl={HasJobUrl}, HasJobDescription={HasJobDescription}",
|
|
request.CvDocumentId,
|
|
!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);
|
|
|
|
return await ProxyResponseAsync(response, ct);
|
|
}
|
|
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
|
{
|
|
_logger.LogWarning("Job match proxy request was cancelled by the client.");
|
|
return StatusCode(499, new { error = "Request cancelled." });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Job match proxy request failed.");
|
|
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private static async Task<ContentResult> ProxyResponseAsync(HttpResponseMessage response, CancellationToken ct)
|
|
{
|
|
var body = await response.Content.ReadAsStringAsync(ct);
|
|
return new ContentResult
|
|
{
|
|
StatusCode = (int)response.StatusCode,
|
|
Content = body,
|
|
ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/json"
|
|
};
|
|
}
|
|
}
|