diff --git a/api-models/Requests/UploadCvRequest.cs b/api-models/Requests/UploadCvRequest.cs
index 70c6b7a..18bd5db 100644
--- a/api-models/Requests/UploadCvRequest.cs
+++ b/api-models/Requests/UploadCvRequest.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Http;
-using System.ComponentModel.DataAnnotations;
+using Shared.Models.Requests;
namespace Models.Requests
{
- public sealed class UploadCvRequest
+ public sealed class UploadCvRequest : UploadFileRequest
{
- [Required]
- public IFormFile Cv { get; set; } = default!;
-
public bool GdprConsent { get; set; }
public string? CaptchaToken { get; set; }
+
}
}
diff --git a/api-models/api-models.csproj b/api-models/api-models.csproj
index d7c7a21..f9c95a3 100644
--- a/api-models/api-models.csproj
+++ b/api-models/api-models.csproj
@@ -11,4 +11,8 @@
+
+
+
+
diff --git a/api/Clients/Api/Contracts/ICvMatcherApi.cs b/api/Clients/Api/Contracts/ICvMatcherApi.cs
index f7bb53e..e3e3a21 100644
--- a/api/Clients/Api/Contracts/ICvMatcherApi.cs
+++ b/api/Clients/Api/Contracts/ICvMatcherApi.cs
@@ -1,6 +1,6 @@
-using Refit;
-using Models.Requests;
using CvMatcher.Models.Responses;
+using Models.Requests;
+using Refit;
namespace Api.Clients.Api.Contracts;
@@ -8,8 +8,8 @@ public interface ICvMatcherApi
{
[Multipart]
[Post("/api/cv/upload")]
- Task Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent);
+ Task Upload([AliasAs("file")] StreamPart file, CancellationToken ct);
[Post("/api/cv/match-job")]
- Task MatchJob([Body] JobMatchRequest request);
+ Task MatchJob([Body] JobMatchRequest request, CancellationToken ct);
}
diff --git a/api/Controllers/CvMatcherController.cs b/api/Controllers/CvMatcherController.cs
index 6a8c3a3..c9f63c5 100644
--- a/api/Controllers/CvMatcherController.cs
+++ b/api/Controllers/CvMatcherController.cs
@@ -47,12 +47,12 @@ public sealed class CvMatcherController : ControllerBase
[FromForm] UploadCvRequest request,
CancellationToken ct)
{
- if (request.Cv is null)
+ if (request.File is null)
{
return BadRequest(new { error = "Missing CV PDF." });
}
- var cv = request.Cv;
+ var cv = request.File;
var gdprConsent = request.GdprConsent;
try
@@ -65,12 +65,14 @@ public sealed class CvMatcherController : ControllerBase
return BadRequest(new { error = "Captcha verification failed." });
}
+ if (!gdprConsent) throw new InvalidOperationException("GDPR consent is required.");
+
_logger.LogInformation("Proxying CV upload to cv-matcher-api. FileName={FileName}, Size={SizeBytes}, GdprConsent={GdprConsent}",
cv.FileName, cv.Length, gdprConsent);
var stream = cv.OpenReadStream();
var part = new Refit.StreamPart(stream, cv.FileName, "application/pdf");
- var res = await _cvApi.Upload(part, gdprConsent);
+ var res = await _cvApi.Upload(part, ct);
return Ok(res);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
@@ -114,7 +116,7 @@ public sealed class CvMatcherController : ControllerBase
request.CvDocumentId,
!string.IsNullOrWhiteSpace(request.JobUrl),
!string.IsNullOrWhiteSpace(request.JobDescription));
- var res = await _cvApi.MatchJob(request);
+ var res = await _cvApi.MatchJob(request, ct);
return Ok(res);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
diff --git a/cv-matcher-api/Controllers/CvController.cs b/cv-matcher-api/Controllers/CvController.cs
index edc8d95..0eb3a93 100644
--- a/cv-matcher-api/Controllers/CvController.cs
+++ b/cv-matcher-api/Controllers/CvController.cs
@@ -2,6 +2,7 @@ using CvMatcher.Models.Requests;
using Api.Services.Contracts;
using Microsoft.AspNetCore.Mvc;
using CvMatcher.Models.Responses;
+using Shared.Models.Requests;
namespace Api.Controllers;
@@ -20,13 +21,13 @@ public sealed class CvController : ControllerBase
[HttpPost("upload")]
[RequestSizeLimit(10 * 1024 * 1024)]
- public async Task> Upload([FromForm(Name = "cv")] IFormFile? cv, [FromForm] bool gdprConsent, CancellationToken ct)
+ public async Task> Upload([FromForm] UploadFileRequest request, CancellationToken ct)
{
try
{
- if (cv is null) return BadRequest(new { error = "Missing CV PDF." });
- _logger.LogInformation("CV upload received. FileName={FileName}, Size={SizeBytes}, GdprConsent={GdprConsent}", cv.FileName, cv.Length, gdprConsent);
- var result = await _service.UploadCvAsync(cv, gdprConsent, ct);
+ if (request.File is null) return BadRequest(new { error = "Missing CV PDF." });
+ _logger.LogInformation("CV upload received. FileName={FileName}, Size={SizeBytes}", request.File.FileName, request.File.Length);
+ var result = await _service.UploadCvAsync(request.File, ct);
_logger.LogInformation("CV upload processed. CvDocumentId={CvDocumentId}, Cached={Cached}", result.DocumentId, result.Cached);
return Ok(result);
}
diff --git a/cv-matcher-api/Services/Contracts/ICvMatcherService.cs b/cv-matcher-api/Services/Contracts/ICvMatcherService.cs
index 86ac599..9c483a5 100644
--- a/cv-matcher-api/Services/Contracts/ICvMatcherService.cs
+++ b/cv-matcher-api/Services/Contracts/ICvMatcherService.cs
@@ -5,7 +5,7 @@ namespace Api.Services.Contracts;
public interface ICvMatcherService
{
- Task UploadCvAsync(IFormFile file, bool gdprConsent, CancellationToken ct);
+ Task UploadCvAsync(IFormFile file, CancellationToken ct);
Task MatchJobAsync(MatchJobRequest request, CancellationToken ct);
Task FindJobsAsync(FindJobsRequest request, CancellationToken ct);
}
diff --git a/cv-matcher-api/Services/CvMatcherService.cs b/cv-matcher-api/Services/CvMatcherService.cs
index a853256..43d5bff 100644
--- a/cv-matcher-api/Services/CvMatcherService.cs
+++ b/cv-matcher-api/Services/CvMatcherService.cs
@@ -32,9 +32,8 @@ public sealed class CvMatcherService : ICvMatcherService
_settings = options.Value;
}
- public async Task UploadCvAsync(IFormFile file, bool gdprConsent, CancellationToken ct)
+ public async Task UploadCvAsync(IFormFile file, CancellationToken ct)
{
- if (!gdprConsent) throw new InvalidOperationException("GDPR consent is required.");
var response = await _rag.IndexCvPdfAsync(file, ct);
return new CvUploadResponse
{
diff --git a/rag-api-models/Requests/IndexDocumentRequest.cs b/rag-api-models/Requests/IndexDocumentRequest.cs
index 422ca95..4c01e96 100644
--- a/rag-api-models/Requests/IndexDocumentRequest.cs
+++ b/rag-api-models/Requests/IndexDocumentRequest.cs
@@ -1,6 +1,6 @@
namespace Rag.Models.Requests
{
- public sealed class IndexDocumentRequest
+ public class IndexDocumentRequest
{
public string? Text { get; set; }
public string? SourceUrl { get; set; }
diff --git a/rag-api-models/Requests/IndexDocumentUploadRequest.cs b/rag-api-models/Requests/IndexDocumentUploadRequest.cs
new file mode 100644
index 0000000..e5578b9
--- /dev/null
+++ b/rag-api-models/Requests/IndexDocumentUploadRequest.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.Http;
+
+namespace Rag.Models.Requests
+{
+ public sealed class IndexDocumentUploadRequest: IndexDocumentRequest
+ {
+ public IFormFile? File { get; set; }
+ }
+}
diff --git a/rag-api/Controllers/RagController.cs b/rag-api/Controllers/RagController.cs
index e6ceafd..1027b93 100644
--- a/rag-api/Controllers/RagController.cs
+++ b/rag-api/Controllers/RagController.cs
@@ -21,21 +21,17 @@ public sealed class RagController : ControllerBase
[HttpPost("documents")]
[RequestSizeLimit(10 * 1024 * 1024)]
public async Task> IndexDocument(
- [FromForm] IFormFile? file,
- [FromForm] string? text,
- [FromForm] string? documentType,
- [FromForm] string? title,
- [FromForm] string? sourceUrl,
+ [FromForm] IndexDocumentUploadRequest request,
CancellationToken ct)
{
try
{
_logger.LogInformation("Index document request received. HasFile={HasFile}, DocumentType={DocumentType}, Title={Title}, SourceUrl={SourceUrl}",
- file is not null, documentType, title, sourceUrl);
+ request.File is not null, request.DocumentType, request.Title, request.SourceUrl);
- if (file is not null)
+ if (request.File is not null)
{
- var result = await _ragService.IndexPdfAsync(file, documentType, title, sourceUrl, ct);
+ var result = await _ragService.IndexPdfAsync(request.File, request.DocumentType, request.Title, request.SourceUrl, ct);
_logger.LogInformation("Indexed PDF document. DocumentId={DocumentId}, DocumentType={DocumentType}, Chunks={Chunks}, Cached={Cached}",
result.DocumentId, result.DocumentType, result.Chunks, result.Cached);
return Ok(result);
@@ -43,10 +39,10 @@ public sealed class RagController : ControllerBase
var textResult = await _ragService.IndexTextAsync(new IndexDocumentRequest
{
- Text = text,
- DocumentType = documentType,
- Title = title,
- SourceUrl = sourceUrl
+ Text = request.Text,
+ DocumentType = request.DocumentType,
+ Title = request.Title,
+ SourceUrl = request.SourceUrl
}, ct);
_logger.LogInformation("Indexed text document. DocumentId={DocumentId}, DocumentType={DocumentType}, Chunks={Chunks}, Cached={Cached}",
textResult.DocumentId, textResult.DocumentType, textResult.Chunks, textResult.Cached);
diff --git a/shared-models/Requests/UploadFileRequest.cs b/shared-models/Requests/UploadFileRequest.cs
new file mode 100644
index 0000000..c9ed0b2
--- /dev/null
+++ b/shared-models/Requests/UploadFileRequest.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Http;
+using System.ComponentModel.DataAnnotations;
+
+namespace Shared.Models.Requests
+{
+ public class UploadFileRequest
+ {
+ [Required]
+ public IFormFile File { get; set; } = default!;
+ }
+}
diff --git a/shared-models/shared-models.csproj b/shared-models/shared-models.csproj
index 9e05988..0cea718 100644
--- a/shared-models/shared-models.csproj
+++ b/shared-models/shared-models.csproj
@@ -7,4 +7,8 @@
enable
+
+
+
+