diff --git a/api/Controllers/ContactController.cs b/api/Controllers/ContactController.cs index bbb35e0..9ccf289 100644 --- a/api/Controllers/ContactController.cs +++ b/api/Controllers/ContactController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.Options; using Models.Settings; using Models.Requests; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -42,6 +43,13 @@ namespace Api.Controllers /// [HttpPost] [EnableRateLimiting("contact")] + [SwaggerOperation(Summary = "Send contact message", Description = "Validates captcha and sends a contact message using the configured email sender.")] + [SwaggerResponse(StatusCodes.Status200OK, "Contact message sent")] + [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)] public async Task Send([FromBody] ContactRequest req, CancellationToken ct) { if (!ModelState.IsValid) @@ -76,6 +84,13 @@ namespace Api.Controllers /// [HttpPost("subscribe")] [EnableRateLimiting("contact")] + [SwaggerOperation(Summary = "Subscribe email", Description = "Validates captcha and subscribes an email address to the mailing list.")] + [SwaggerResponse(StatusCodes.Status200OK, "Subscription succeeded")] + [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)] public async Task Subscribe([FromBody] SubscribeRequest req, CancellationToken ct) { if (!ModelState.IsValid) diff --git a/api/Controllers/FileDownloadController.cs b/api/Controllers/FileDownloadController.cs index 4ac6f7f..2601843 100644 --- a/api/Controllers/FileDownloadController.cs +++ b/api/Controllers/FileDownloadController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -47,10 +48,19 @@ namespace Api.Controllers /// File not found /// Requested range not satisfiable [HttpGet("{fileName?}")] + [SwaggerOperation(Summary = "Download file", Description = "Downloads a file with support for full and ranged (resumable) transfers.")] + [SwaggerResponse(StatusCodes.Status200OK, "Full file content returned")] + [SwaggerResponse(StatusCodes.Status206PartialContent, "Partial file content returned for a range request")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "No file name provided and no default configured")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Requested file was not found")] + [SwaggerResponse(StatusCodes.Status416RangeNotSatisfiable, "Requested byte range is invalid")] + [SwaggerResponse(StatusCodes.Status500InternalServerError, "Unexpected server error while downloading")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status206PartialContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status416RangeNotSatisfiable)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task DownloadFile(string? fileName = null) { try diff --git a/api/Controllers/GoogleController.cs b/api/Controllers/GoogleController.cs index 6ae78ca..b5b480d 100644 --- a/api/Controllers/GoogleController.cs +++ b/api/Controllers/GoogleController.cs @@ -2,6 +2,7 @@ using Models.Settings; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -29,6 +30,9 @@ namespace Api.Controllers /// /// 200 OK with the Tag Manager ID as a string. [HttpGet("tagmanager")] + [SwaggerOperation(Summary = "Get Google Tag Manager ID", Description = "Returns the Google Tag Manager ID configured for frontend analytics.")] + [SwaggerResponse(StatusCodes.Status200OK, "Tag Manager ID returned")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetTagManagerId(CancellationToken ct) { return Ok(_googleSettings.TagManagerId); @@ -41,6 +45,9 @@ namespace Api.Controllers /// /// 200 OK with the maps API key as a string. [HttpGet("maps")] + [SwaggerOperation(Summary = "Get Google Maps key", Description = "Returns the Google Maps API key configured for frontend map features.")] + [SwaggerResponse(StatusCodes.Status200OK, "Maps API key returned")] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task GetMapKey(CancellationToken ct) { return Ok(_googleSettings.MapKey); diff --git a/api/Controllers/HealthController.cs b/api/Controllers/HealthController.cs index 5c8b273..0848a02 100644 --- a/api/Controllers/HealthController.cs +++ b/api/Controllers/HealthController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -22,6 +23,9 @@ namespace Api.Controllers /// // GET api/health/live [HttpGet("live")] + [SwaggerOperation(Summary = "Liveness probe", Description = "Returns whether the API process is alive.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is alive")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Live() => Ok(new { status = "alive" }); /// @@ -33,6 +37,9 @@ namespace Api.Controllers /// // GET api/health [HttpGet] + [SwaggerOperation(Summary = "Health check", Description = "Returns overall health status and current UTC time.")] + [SwaggerResponse(StatusCodes.Status200OK, "Health check succeeded")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Health() => Ok(new { status = "ok", time = DateTimeOffset.UtcNow }); /// @@ -43,6 +50,9 @@ namespace Api.Controllers /// 200 OK with the same JSON payload provided in the request body. // POST api/health/echo [HttpPost("echo")] + [SwaggerOperation(Summary = "Echo payload", Description = "Returns the same JSON payload received in the request body.")] + [SwaggerResponse(StatusCodes.Status200OK, "Payload echoed successfully")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Echo(object payload) => Ok(payload); /// @@ -55,6 +65,11 @@ namespace Api.Controllers /// // GET api/health/ready [HttpGet("ready")] + [SwaggerOperation(Summary = "Readiness probe", Description = "Returns whether the service is ready to accept traffic.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is ready")] + [SwaggerResponse(StatusCodes.Status503ServiceUnavailable, "Service is not ready")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public IActionResult Ready() { var ready = true; diff --git a/cv-matcher-api/Controllers/CvController.cs b/cv-matcher-api/Controllers/CvController.cs index 0eb3a93..edd343d 100644 --- a/cv-matcher-api/Controllers/CvController.cs +++ b/cv-matcher-api/Controllers/CvController.cs @@ -3,6 +3,7 @@ using Api.Services.Contracts; using Microsoft.AspNetCore.Mvc; using CvMatcher.Models.Responses; using Shared.Models.Requests; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers; @@ -21,6 +22,11 @@ public sealed class CvController : ControllerBase [HttpPost("upload")] [RequestSizeLimit(10 * 1024 * 1024)] + [SwaggerOperation(Summary = "Upload CV document", Description = "Uploads a CV PDF and indexes it for matching.")] + [SwaggerResponse(StatusCodes.Status200OK, "CV uploaded and indexed successfully")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid upload request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> Upload([FromForm] UploadFileRequest request, CancellationToken ct) { try @@ -39,6 +45,11 @@ public sealed class CvController : ControllerBase } [HttpPost("find-jobs")] + [SwaggerOperation(Summary = "Find matching jobs", Description = "Finds top matching jobs for a previously uploaded CV document.")] + [SwaggerResponse(StatusCodes.Status200OK, "Matching jobs returned")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid find jobs request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> FindJobs([FromBody] FindJobsRequest request, CancellationToken ct) { try @@ -56,6 +67,11 @@ public sealed class CvController : ControllerBase } [HttpPost("match-job")] + [SwaggerOperation(Summary = "Match CV to one job", Description = "Computes detailed match analysis between a CV and a single job description or URL.")] + [SwaggerResponse(StatusCodes.Status200OK, "Job match computed successfully")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid match job request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> MatchJob([FromBody] MatchJobRequest request, CancellationToken ct) { try diff --git a/cv-matcher-api/Controllers/HealthController.cs b/cv-matcher-api/Controllers/HealthController.cs index cb8cd2a..e1932da 100644 --- a/cv-matcher-api/Controllers/HealthController.cs +++ b/cv-matcher-api/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -19,6 +20,9 @@ namespace Api.Controllers /// // GET api/health/live [HttpGet("live")] + [SwaggerOperation(Summary = "Liveness probe", Description = "Returns whether the API process is alive.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is alive")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Live() => Ok(new { status = "alive" }); /// @@ -30,6 +34,9 @@ namespace Api.Controllers /// // GET api/health [HttpGet] + [SwaggerOperation(Summary = "Health check", Description = "Returns overall health status and current UTC time.")] + [SwaggerResponse(StatusCodes.Status200OK, "Health check succeeded")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Health() => Ok(new { status = "ok", time = DateTimeOffset.UtcNow }); /// @@ -40,6 +47,9 @@ namespace Api.Controllers /// 200 OK with the same JSON payload provided in the request body. // POST api/health/echo [HttpPost("echo")] + [SwaggerOperation(Summary = "Echo payload", Description = "Returns the same JSON payload received in the request body.")] + [SwaggerResponse(StatusCodes.Status200OK, "Payload echoed successfully")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Echo(object payload) => Ok(payload); /// @@ -52,6 +62,11 @@ namespace Api.Controllers /// // GET api/health/ready [HttpGet("ready")] + [SwaggerOperation(Summary = "Readiness probe", Description = "Returns whether the service is ready to accept traffic.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is ready")] + [SwaggerResponse(StatusCodes.Status503ServiceUnavailable, "Service is not ready")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public IActionResult Ready() { var ready = true; diff --git a/rag-api/Controllers/HealthController.cs b/rag-api/Controllers/HealthController.cs index cb8cd2a..e1932da 100644 --- a/rag-api/Controllers/HealthController.cs +++ b/rag-api/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers { @@ -19,6 +20,9 @@ namespace Api.Controllers /// // GET api/health/live [HttpGet("live")] + [SwaggerOperation(Summary = "Liveness probe", Description = "Returns whether the API process is alive.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is alive")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Live() => Ok(new { status = "alive" }); /// @@ -30,6 +34,9 @@ namespace Api.Controllers /// // GET api/health [HttpGet] + [SwaggerOperation(Summary = "Health check", Description = "Returns overall health status and current UTC time.")] + [SwaggerResponse(StatusCodes.Status200OK, "Health check succeeded")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Health() => Ok(new { status = "ok", time = DateTimeOffset.UtcNow }); /// @@ -40,6 +47,9 @@ namespace Api.Controllers /// 200 OK with the same JSON payload provided in the request body. // POST api/health/echo [HttpPost("echo")] + [SwaggerOperation(Summary = "Echo payload", Description = "Returns the same JSON payload received in the request body.")] + [SwaggerResponse(StatusCodes.Status200OK, "Payload echoed successfully")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Echo(object payload) => Ok(payload); /// @@ -52,6 +62,11 @@ namespace Api.Controllers /// // GET api/health/ready [HttpGet("ready")] + [SwaggerOperation(Summary = "Readiness probe", Description = "Returns whether the service is ready to accept traffic.")] + [SwaggerResponse(StatusCodes.Status200OK, "Service is ready")] + [SwaggerResponse(StatusCodes.Status503ServiceUnavailable, "Service is not ready")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)] public IActionResult Ready() { var ready = true; diff --git a/rag-api/Controllers/RagController.cs b/rag-api/Controllers/RagController.cs index 1027b93..c0e07f7 100644 --- a/rag-api/Controllers/RagController.cs +++ b/rag-api/Controllers/RagController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Api.Services.Contracts; using Rag.Models.Requests; using Rag.Models.Responses; +using Swashbuckle.AspNetCore.Annotations; namespace Api.Controllers; @@ -20,6 +21,11 @@ public sealed class RagController : ControllerBase [HttpPost("documents")] [RequestSizeLimit(10 * 1024 * 1024)] + [SwaggerOperation(Summary = "Index document (multipart)", Description = "Indexes a PDF file or raw text document using multipart/form-data payload.")] + [SwaggerResponse(StatusCodes.Status200OK, "Document indexed successfully")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid indexing request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> IndexDocument( [FromForm] IndexDocumentUploadRequest request, CancellationToken ct) @@ -56,6 +62,11 @@ public sealed class RagController : ControllerBase } [HttpPost("documents/json")] + [SwaggerOperation(Summary = "Index document (JSON)", Description = "Indexes a text document sent as JSON.")] + [SwaggerResponse(StatusCodes.Status200OK, "JSON document indexed successfully")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid JSON indexing request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> IndexJsonDocument([FromBody] IndexDocumentRequest request, CancellationToken ct) { try @@ -75,6 +86,11 @@ public sealed class RagController : ControllerBase } [HttpPost("search")] + [SwaggerOperation(Summary = "Semantic search", Description = "Performs semantic retrieval over indexed documents.")] + [SwaggerResponse(StatusCodes.Status200OK, "Search results returned")] + [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid search request")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> Search([FromBody] SearchRequest request, CancellationToken ct) { try @@ -93,6 +109,11 @@ public sealed class RagController : ControllerBase } [HttpGet("documents/{id}")] + [SwaggerOperation(Summary = "Get document details", Description = "Returns indexed document details for the provided document id.")] + [SwaggerResponse(StatusCodes.Status200OK, "Document details returned")] + [SwaggerResponse(StatusCodes.Status404NotFound, "Document was not found")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetDocument(string id, CancellationToken ct) { _logger.LogInformation("Get document request received. DocumentId={DocumentId}", id);