From b154fe51c33628b9ba1e175f426b2c1c9d04c26a Mon Sep 17 00:00:00 2001 From: Gelu Mihes Date: Wed, 6 May 2026 17:45:05 +0300 Subject: [PATCH] Changes --- api/Clients/Api/Contracts/ICvMatcherApi.cs | 5 +- api/Controllers/CvMatcherController.cs | 20 ++------ api/Services/Contracts/IEmailSender.cs | 1 + api/Services/SmtpEmailSender.cs | 5 ++ api/api.csproj | 1 + .../Requests/MatchJobRequest.cs | 1 - cv-matcher-api/Controllers/CvController.cs | 11 +++-- cv-matcher-api/Program.cs | 1 - .../Services/Contracts/IEmailService.cs | 6 --- cv-matcher-api/Services/CvMatcherService.cs | 43 ++++++++--------- cv-matcher-api/Services/EmailService.cs | 46 ------------------- .../RagDocumentDetailsResponse.cs} | 4 +- rag-api/Controllers/RagController.cs | 9 ++-- rag-api/Services/Contracts/IRagService.cs | 3 +- rag-api/Services/RagService.cs | 4 +- 15 files changed, 50 insertions(+), 110 deletions(-) delete mode 100644 cv-matcher-api/Services/Contracts/IEmailService.cs delete mode 100644 cv-matcher-api/Services/EmailService.cs rename rag-api-models/{RagDocumentDetails.cs => Responses/RagDocumentDetailsResponse.cs} (82%) diff --git a/api/Clients/Api/Contracts/ICvMatcherApi.cs b/api/Clients/Api/Contracts/ICvMatcherApi.cs index 390e829..f7bb53e 100644 --- a/api/Clients/Api/Contracts/ICvMatcherApi.cs +++ b/api/Clients/Api/Contracts/ICvMatcherApi.cs @@ -1,5 +1,6 @@ using Refit; using Models.Requests; +using CvMatcher.Models.Responses; namespace Api.Clients.Api.Contracts; @@ -7,8 +8,8 @@ public interface ICvMatcherApi { [Multipart] [Post("/api/cv/upload")] - Task Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent); + Task Upload([AliasAs("cv")] StreamPart cv, [AliasAs("gdprConsent")] bool gdprConsent); [Post("/api/cv/match-job")] - Task MatchJob([Body] JobMatchRequest request); + Task MatchJob([Body] JobMatchRequest request); } diff --git a/api/Controllers/CvMatcherController.cs b/api/Controllers/CvMatcherController.cs index 07488dc..6a8c3a3 100644 --- a/api/Controllers/CvMatcherController.cs +++ b/api/Controllers/CvMatcherController.cs @@ -70,8 +70,8 @@ public sealed class CvMatcherController : ControllerBase 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); + var res = await _cvApi.Upload(part, gdprConsent); + return Ok(res); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { @@ -114,9 +114,8 @@ public sealed class CvMatcherController : ControllerBase request.CvDocumentId, !string.IsNullOrWhiteSpace(request.JobUrl), !string.IsNullOrWhiteSpace(request.JobDescription)); - - using var response = await _cvApi.MatchJob(request); - return await ProxyResponseAsync(response, ct); + var res = await _cvApi.MatchJob(request); + return Ok(res); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { @@ -129,15 +128,4 @@ public sealed class CvMatcherController : ControllerBase return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." }); } } - - private static async Task 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" - }; - } } diff --git a/api/Services/Contracts/IEmailSender.cs b/api/Services/Contracts/IEmailSender.cs index ddd422a..ec79fcc 100644 --- a/api/Services/Contracts/IEmailSender.cs +++ b/api/Services/Contracts/IEmailSender.cs @@ -7,5 +7,6 @@ namespace Api.Services.Contracts Task SendContactAsync(ContactRequest req, CancellationToken ct); Task SendSubscribeAsync(SubscribeRequest req, CancellationToken ct); Task SendFileDownloadNotificationAsync(string fileName, string? userIp, CancellationToken ct); + Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct); } } diff --git a/api/Services/SmtpEmailSender.cs b/api/Services/SmtpEmailSender.cs index 1860921..2e37ebd 100644 --- a/api/Services/SmtpEmailSender.cs +++ b/api/Services/SmtpEmailSender.cs @@ -166,5 +166,10 @@ namespace Api.Services await client.SendAsync(message, ct); await client.DisconnectAsync(true, ct); } + + public Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct) + { + throw new NotImplementedException(); + } } } diff --git a/api/api.csproj b/api/api.csproj index 7d05a8c..44d0301 100644 --- a/api/api.csproj +++ b/api/api.csproj @@ -36,6 +36,7 @@ + diff --git a/cv-matcher-api-models/Requests/MatchJobRequest.cs b/cv-matcher-api-models/Requests/MatchJobRequest.cs index 3f72363..c3b837c 100644 --- a/cv-matcher-api-models/Requests/MatchJobRequest.cs +++ b/cv-matcher-api-models/Requests/MatchJobRequest.cs @@ -7,6 +7,5 @@ public string? JobDescription { get; set; } public bool GdprConsent { get; set; } public string? Email { get; set; } - public string? CaptchaToken { get; set; } } } diff --git a/cv-matcher-api/Controllers/CvController.cs b/cv-matcher-api/Controllers/CvController.cs index 1f4e44d..edc8d95 100644 --- a/cv-matcher-api/Controllers/CvController.cs +++ b/cv-matcher-api/Controllers/CvController.cs @@ -1,6 +1,7 @@ using CvMatcher.Models.Requests; using Api.Services.Contracts; using Microsoft.AspNetCore.Mvc; +using CvMatcher.Models.Responses; namespace Api.Controllers; @@ -19,7 +20,7 @@ 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(Name = "cv")] IFormFile? cv, [FromForm] bool gdprConsent, CancellationToken ct) { try { @@ -37,14 +38,14 @@ public sealed class CvController : ControllerBase } [HttpPost("find-jobs")] - public async Task FindJobs([FromBody] FindJobsRequest request, CancellationToken ct) + public async Task> FindJobs([FromBody] FindJobsRequest request, CancellationToken ct) { try { _logger.LogInformation("Find jobs request received. CvDocumentId={CvDocumentId}, TopK={TopK}", request.CvDocumentId, request.TopK); var result = await _service.FindJobsAsync(request, ct); _logger.LogInformation("Find jobs completed. CvDocumentId={CvDocumentId}, ResultCount={ResultCount}", request.CvDocumentId, result.Jobs.Count); - return Ok(result); + return result; } catch (InvalidOperationException ex) { @@ -54,7 +55,7 @@ public sealed class CvController : ControllerBase } [HttpPost("match-job")] - public async Task MatchJob([FromBody] MatchJobRequest request, CancellationToken ct) + public async Task> MatchJob([FromBody] MatchJobRequest request, CancellationToken ct) { try { @@ -62,7 +63,7 @@ public sealed class CvController : ControllerBase request.CvDocumentId, !string.IsNullOrWhiteSpace(request.JobUrl), !string.IsNullOrWhiteSpace(request.JobDescription), !string.IsNullOrWhiteSpace(request.Email)); var result = await _service.MatchJobAsync(request, ct); _logger.LogInformation("Match job completed. CvDocumentId={CvDocumentId}, Score={Score}, Cached={Cached}", request.CvDocumentId, result.Score, result.Cached); - return Ok(result); + return result; } catch (InvalidOperationException ex) { diff --git a/cv-matcher-api/Program.cs b/cv-matcher-api/Program.cs index 792770d..a67fe2e 100644 --- a/cv-matcher-api/Program.cs +++ b/cv-matcher-api/Program.cs @@ -97,7 +97,6 @@ try ?? throw new InvalidOperationException("Connection string 'CvMatcherDb' is missing."))); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddSingleton(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); diff --git a/cv-matcher-api/Services/Contracts/IEmailService.cs b/cv-matcher-api/Services/Contracts/IEmailService.cs deleted file mode 100644 index 7eecca5..0000000 --- a/cv-matcher-api/Services/Contracts/IEmailService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Api.Services.Contracts; - -public interface IEmailService -{ - Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct); -} diff --git a/cv-matcher-api/Services/CvMatcherService.cs b/cv-matcher-api/Services/CvMatcherService.cs index 3225344..a853256 100644 --- a/cv-matcher-api/Services/CvMatcherService.cs +++ b/cv-matcher-api/Services/CvMatcherService.cs @@ -16,7 +16,6 @@ public sealed class CvMatcherService : ICvMatcherService private readonly IJobTextExtractor _jobTextExtractor; private readonly IMatcherAiClient _ai; private readonly IMatcherRepository _repository; - private readonly IEmailService _email; private readonly MatcherSettings _settings; public CvMatcherService( @@ -24,14 +23,12 @@ public sealed class CvMatcherService : ICvMatcherService IJobTextExtractor jobTextExtractor, IMatcherAiClient ai, IMatcherRepository repository, - IEmailService email, IOptions options) { _rag = rag; _jobTextExtractor = jobTextExtractor; _ai = ai; _repository = repository; - _email = email; _settings = options.Value; } @@ -138,11 +135,11 @@ public sealed class CvMatcherService : ICvMatcherService result.Cached = false; await _repository.SaveMatchAsync(cv.Id, job.Id, result, ct); - await _email.SendMatchAsync( - email, - $"MyAi.ro CV Match: {result.Score}% - {job.Title}", - BuildEmailBody(cv, job, result), - ct); + //await _email.SendMatchAsync( + // email, + // $"MyAi.ro CV Match: {result.Score}% - {job.Title}", + // BuildEmailBody(cv, job, result), + // ct); return result; } @@ -181,24 +178,24 @@ public sealed class CvMatcherService : ICvMatcherService private static string Limit(string value, int max) => value.Length <= max ? value : value[..max]; - private static string BuildEmailBody(RagDocumentDetails cv, RagDocumentDetails job, JobMatchResponse result) => $""" - CV Matcher result + //private static string BuildEmailBody(RagDocumentDetails cv, RagDocumentDetails job, JobMatchResponse result) => $""" + // CV Matcher result - CV: {cv.Title} - Job: {job.Title} - Job URL: {job.SourceUrl ?? "N/A"} - Score: {result.Score}% + // CV: {cv.Title} + // Job: {job.Title} + // Job URL: {job.SourceUrl ?? "N/A"} + // Score: {result.Score}% - Summary: - {result.Summary} + // Summary: + // {result.Summary} - Strengths: - - {string.Join("\n- ", result.Strengths)} + // Strengths: + // - {string.Join("\n- ", result.Strengths)} - Gaps: - - {string.Join("\n- ", result.Gaps)} + // Gaps: + // - {string.Join("\n- ", result.Gaps)} - Recommendations: - - {string.Join("\n- ", result.Recommendations)} - """; + // Recommendations: + // - {string.Join("\n- ", result.Recommendations)} + // """; } diff --git a/cv-matcher-api/Services/EmailService.cs b/cv-matcher-api/Services/EmailService.cs deleted file mode 100644 index ff407e1..0000000 --- a/cv-matcher-api/Services/EmailService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using CvMatcher.Models.Settings; -using Api.Services.Contracts; -using MailKit.Net.Smtp; -using MailKit.Security; -using Microsoft.Extensions.Options; -using MimeKit; - -namespace Api.Services; - -public sealed class EmailService : IEmailService -{ - private readonly SmtpSettings _settings; - private readonly ILogger _logger; - - public EmailService(IOptions options, ILogger logger) - { - _settings = options.Value; - _logger = logger; - } - - public async Task SendMatchAsync(string? explicitTo, string subject, string body, CancellationToken ct) - { - var to = !string.IsNullOrWhiteSpace(explicitTo) ? explicitTo : _settings.ToEmail; - if (string.IsNullOrWhiteSpace(_settings.Host) || string.IsNullOrWhiteSpace(to)) - { - _logger.LogInformation("SMTP is not configured. Skipping CV matcher email."); - return; - } - - var message = new MimeMessage(); - message.From.Add(MailboxAddress.Parse(_settings.FromEmail)); - message.To.Add(MailboxAddress.Parse(to)); - message.Subject = subject; - message.Body = new TextPart("plain") { Text = body }; - - using var client = new SmtpClient(); - var secureSocket = _settings.UseStartTls ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; - await client.ConnectAsync(_settings.Host, _settings.Port, secureSocket, ct); - if (!string.IsNullOrWhiteSpace(_settings.Username)) - { - await client.AuthenticateAsync(_settings.Username, _settings.Password, ct); - } - await client.SendAsync(message, ct); - await client.DisconnectAsync(true, ct); - } -} diff --git a/rag-api-models/RagDocumentDetails.cs b/rag-api-models/Responses/RagDocumentDetailsResponse.cs similarity index 82% rename from rag-api-models/RagDocumentDetails.cs rename to rag-api-models/Responses/RagDocumentDetailsResponse.cs index 8c87330..9fffa07 100644 --- a/rag-api-models/RagDocumentDetails.cs +++ b/rag-api-models/Responses/RagDocumentDetailsResponse.cs @@ -1,6 +1,6 @@ -namespace Rag.Models +namespace Rag.Models.Responses { - public sealed class RagDocumentDetails + public sealed class RagDocumentDetailsResponse { public required string Id { get; init; } public required string DocumentType { get; init; } diff --git a/rag-api/Controllers/RagController.cs b/rag-api/Controllers/RagController.cs index 802c24e..e6ceafd 100644 --- a/rag-api/Controllers/RagController.cs +++ b/rag-api/Controllers/RagController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Api.Services.Contracts; using Rag.Models.Requests; +using Rag.Models.Responses; namespace Api.Controllers; @@ -19,7 +20,7 @@ public sealed class RagController : ControllerBase [HttpPost("documents")] [RequestSizeLimit(10 * 1024 * 1024)] - public async Task IndexDocument( + public async Task> IndexDocument( [FromForm] IFormFile? file, [FromForm] string? text, [FromForm] string? documentType, @@ -59,7 +60,7 @@ public sealed class RagController : ControllerBase } [HttpPost("documents/json")] - public async Task IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct) + public async Task> IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct) { try { @@ -78,7 +79,7 @@ public sealed class RagController : ControllerBase } [HttpPost("search")] - public async Task Search([FromBody] SearchRequest request, CancellationToken ct) + public async Task> Search([FromBody] SearchRequest request, CancellationToken ct) { try { @@ -96,7 +97,7 @@ public sealed class RagController : ControllerBase } [HttpGet("documents/{id}")] - public async Task GetDocument(string id, CancellationToken ct) + public async Task> GetDocument(string id, CancellationToken ct) { _logger.LogInformation("Get document request received. DocumentId={DocumentId}", id); var document = await _ragService.GetDocumentAsync(id, ct); diff --git a/rag-api/Services/Contracts/IRagService.cs b/rag-api/Services/Contracts/IRagService.cs index 3358e6c..3d68812 100644 --- a/rag-api/Services/Contracts/IRagService.cs +++ b/rag-api/Services/Contracts/IRagService.cs @@ -1,4 +1,3 @@ -using Rag.Models; using Rag.Models.Requests; using Rag.Models.Responses; @@ -9,5 +8,5 @@ public interface IRagService Task IndexTextAsync(IndexDocumentRequest request, CancellationToken ct); Task IndexPdfAsync(IFormFile file, string? documentType, string? title, string? sourceUrl, CancellationToken ct); Task SearchAsync(SearchRequest request, CancellationToken ct); - Task GetDocumentAsync(string documentId, CancellationToken ct); + Task GetDocumentAsync(string documentId, CancellationToken ct); } diff --git a/rag-api/Services/RagService.cs b/rag-api/Services/RagService.cs index 63c3842..f744d4b 100644 --- a/rag-api/Services/RagService.cs +++ b/rag-api/Services/RagService.cs @@ -97,10 +97,10 @@ public sealed class RagService : IRagService return new SearchResponse { Results = results }; } - public async Task GetDocumentAsync(string documentId, CancellationToken ct) + public async Task GetDocumentAsync(string documentId, CancellationToken ct) { var document = await _repository.GetDocumentByIdAsync(documentId, ct); - return document is null ? null : new RagDocumentDetails + return document is null ? null : new RagDocumentDetailsResponse { Id = document.Id, DocumentType = document.DocumentType,