Changes
Build and Push Docker Images / build (push) Failing after 0s

This commit is contained in:
2026-05-08 13:46:25 +03:00
parent 86d4d2af06
commit 51e668bf1d
10 changed files with 84 additions and 44 deletions
+13 -3
View File
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
using Models.Settings;
using Swashbuckle.AspNetCore.Annotations;
using Models.Requests;
using Shared.Models.Responses;
namespace Api.Controllers
{
@@ -42,17 +43,26 @@ namespace Api.Controllers
[HttpPost("verify")]
[SwaggerOperation(Summary = "Verify captcha token")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[SwaggerResponse(StatusCodes.Status400BadRequest, "Captcha verification failed or token missing", typeof(ErrorResponse))]
public async Task<IActionResult> Verify([FromBody] CaptchaVerifyRequest req, CancellationToken ct)
{
if (req is null || string.IsNullOrWhiteSpace(req.Token)) return BadRequest(new { error = "Missing token" });
if (req is null || string.IsNullOrWhiteSpace(req.Token))
{
return BadRequest(new ErrorResponse { Error = "Missing token", Code = "captcha_token_missing" });
}
var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
var verdict = await _captcha.VerifyAsync(req.Token, userIp, req.ExpectedAction, ct);
if (!verdict.Success)
{
_log.LogWarning("Captcha failed. ip={Ip} score={Score} err={Err}", userIp, verdict.Score, verdict.Error);
return BadRequest(new { error = "Captcha verification failed.", score = verdict.Score });
return BadRequest(new ErrorResponse
{
Error = "Captcha verification failed.",
Code = "captcha_verification_failed",
Score = verdict.Score
});
}
return Ok(verdict);
+15 -8
View File
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Options;
using Models.Settings;
using Models.Requests;
using Swashbuckle.AspNetCore.Annotations;
using Shared.Models.Responses;
namespace Api.Controllers
{
@@ -48,8 +49,8 @@ namespace Api.Controllers
[SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request or captcha verification failed")]
[SwaggerResponse(StatusCodes.Status500InternalServerError, "Contact message could not be sent due to server error")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> Send([FromBody] ContactRequest req, CancellationToken ct)
{
if (!ModelState.IsValid)
@@ -57,7 +58,10 @@ namespace Api.Controllers
var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
var verdict = await _captcha.VerifyAsync(req.CaptchaToken, userIp, "contact", ct);
if (!verdict.Success) return BadRequest("Captcha verification failed.");
if (!verdict.Success)
{
return BadRequest(new ErrorResponse { Error = "Captcha verification failed.", Code = "captcha_verification_failed" });
}
try
{
@@ -67,7 +71,7 @@ namespace Api.Controllers
catch (Exception ex)
{
_log.LogError(ex, "Contact send failed. ip={Ip} from={From}", userIp, req.Email);
return StatusCode(500, "Could not send message.");
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse { Error = "Could not send message.", Code = "contact_send_failed" });
}
}
@@ -89,8 +93,8 @@ namespace Api.Controllers
[SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request or captcha verification failed")]
[SwaggerResponse(StatusCodes.Status500InternalServerError, "Subscription failed due to server error")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> Subscribe([FromBody] SubscribeRequest req, CancellationToken ct)
{
if (!ModelState.IsValid)
@@ -98,7 +102,10 @@ namespace Api.Controllers
var userIp = HttpContext.Connection.RemoteIpAddress?.ToString();
var verdict = await _captcha.VerifyAsync(req.CaptchaToken, userIp, "contact", ct);
if (!verdict.Success) return BadRequest("Captcha verification failed.");
if (!verdict.Success)
{
return BadRequest(new ErrorResponse { Error = "Captcha verification failed.", Code = "captcha_verification_failed" });
}
try
{
@@ -108,7 +115,7 @@ namespace Api.Controllers
catch (Exception ex)
{
_log.LogError(ex, "Subscription failed. ip={Ip} eMail={eMail}", userIp, req.Email);
return StatusCode(500, "Failed.");
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse { Error = "Failed.", Code = "subscription_failed" });
}
}
+14 -11
View File
@@ -4,6 +4,7 @@ using Api.Services.Contracts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Swashbuckle.AspNetCore.Annotations;
using Shared.Models.Responses;
namespace Api.Controllers;
@@ -37,8 +38,9 @@ public sealed class CvMatcherController : ControllerBase
[HttpPost("upload")]
[RequestSizeLimit(8 * 1024 * 1024)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status502BadGateway)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status502BadGateway)]
[ProducesResponseType(typeof(ErrorResponse), 499)]
[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")]
@@ -49,7 +51,7 @@ public sealed class CvMatcherController : ControllerBase
{
if (request.File is null)
{
return BadRequest(new { error = "Missing CV PDF." });
return BadRequest(new ErrorResponse { Error = "Missing CV PDF.", Code = "cv_file_missing" });
}
var cv = request.File;
@@ -62,7 +64,7 @@ public sealed class CvMatcherController : ControllerBase
if (!verdict.Success)
{
_logger.LogWarning("Captcha verification failed for CV upload. IP={IP}", userIp);
return BadRequest(new { error = "Captcha verification failed." });
return BadRequest(new ErrorResponse { Error = "Captcha verification failed.", Code = "captcha_verification_failed" });
}
if (!gdprConsent) throw new InvalidOperationException("GDPR consent is required.");
@@ -78,12 +80,12 @@ public sealed class CvMatcherController : ControllerBase
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
_logger.LogWarning("CV upload proxy request was cancelled by the client.");
return StatusCode(499, new { error = "Request cancelled." });
return StatusCode(499, new ErrorResponse { Error = "Request cancelled.", Code = "request_cancelled" });
}
catch (Exception ex)
{
_logger.LogError(ex, "CV upload proxy request failed.");
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
return StatusCode(StatusCodes.Status502BadGateway, new ErrorResponse { Error = "CV matcher API request failed.", Code = "upstream_request_failed" });
}
}
@@ -94,8 +96,9 @@ public sealed class CvMatcherController : ControllerBase
/// <param name="ct">Cancellation token.</param>
[HttpPost("match-job")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status502BadGateway)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status502BadGateway)]
[ProducesResponseType(typeof(ErrorResponse), 499)]
[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")]
@@ -109,7 +112,7 @@ public sealed class CvMatcherController : ControllerBase
if (!verdict.Success)
{
_logger.LogWarning("Captcha verification failed for job match. IP={IP}", userIp);
return BadRequest(new { error = "Captcha verification failed." });
return BadRequest(new ErrorResponse { Error = "Captcha verification failed.", Code = "captcha_verification_failed" });
}
_logger.LogInformation("Proxying job match request to cv-matcher-api. CvDocumentId={CvDocumentId}, HasJobUrl={HasJobUrl}, HasJobDescription={HasJobDescription}",
@@ -122,12 +125,12 @@ public sealed class CvMatcherController : ControllerBase
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
_logger.LogWarning("Job match proxy request was cancelled by the client.");
return StatusCode(499, new { error = "Request cancelled." });
return StatusCode(499, new ErrorResponse { Error = "Request cancelled.", Code = "request_cancelled" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Job match proxy request failed.");
return StatusCode(StatusCodes.Status502BadGateway, new { error = "CV matcher API request failed." });
return StatusCode(StatusCodes.Status502BadGateway, new ErrorResponse { Error = "CV matcher API request failed.", Code = "upstream_request_failed" });
}
}
}
+7 -6
View File
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using Swashbuckle.AspNetCore.Annotations;
using Shared.Models.Responses;
namespace Api.Controllers
{
@@ -57,10 +58,10 @@ namespace Api.Controllers
[SwaggerResponse(StatusCodes.Status500InternalServerError, "Unexpected server error while downloading")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status206PartialContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status416RangeNotSatisfiable)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> DownloadFile(string? fileName = null)
{
try
@@ -73,7 +74,7 @@ namespace Api.Controllers
if (string.IsNullOrWhiteSpace(fileName))
{
_logger.LogWarning("No file name provided and no default file name configured");
return BadRequest(new { error = "File name is required" });
return BadRequest(new ErrorResponse { Error = "File name is required", Code = "file_name_required" });
}
_logger.LogInformation("Using default file name from settings: {FileName}", fileName);
@@ -102,7 +103,7 @@ namespace Api.Controllers
if (!System.IO.File.Exists(filePath))
{
_logger.LogWarning("File not found: {FilePath}", filePath);
return NotFound(new { error = "File not found" });
return NotFound(new ErrorResponse { Error = "File not found", Code = "file_not_found" });
}
var fileInfo = new FileInfo(filePath);
@@ -150,7 +151,7 @@ namespace Api.Controllers
catch (Exception ex)
{
_logger.LogError(ex, "Error downloading file: {FileName}", fileName);
return StatusCode(StatusCodes.Status500InternalServerError, new { error = "An error occurred while downloading the file" });
return StatusCode(StatusCodes.Status500InternalServerError, new ErrorResponse { Error = "An error occurred while downloading the file", Code = "download_failed" });
}
}