From 934b69b11bd7ef1fd941980f629fa874c6069ba8 Mon Sep 17 00:00:00 2001 From: eee4 <41441600+eee4@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:08:23 +0200 Subject: [PATCH] chore: add API documentation to swagger --- Controllers/AuthController.cs | 31 +++++++++++++++++++++++++ Controllers/QuoteController.cs | 41 ++++++++++++++++++++++++++++++++-- DTOs/ErrorDTO.cs | 7 ++++++ DTOs/QuoteShortDTO.cs | 2 ++ Program.cs | 26 ++++++++++++++++++++- QuotifyBE.csproj | 3 +++ WeatherForecast.cs | 13 ----------- 7 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 DTOs/ErrorDTO.cs delete mode 100644 WeatherForecast.cs diff --git a/Controllers/AuthController.cs b/Controllers/AuthController.cs index 122df1e..9e31013 100644 --- a/Controllers/AuthController.cs +++ b/Controllers/AuthController.cs @@ -10,6 +10,7 @@ namespace QuotifyBE.Controllers; [ApiController] [Route("api/v1/auth")] +[Produces("application/json")] public class AuthController : ControllerBase { @@ -25,7 +26,25 @@ public class AuthController : ControllerBase } // POST /api/v1/auth/login + /// + /// Log-in endpoint + /// + /// + /// Allows to generate a JWT valid for 5 minutes. + /// The token needs to be passed to other, secured endpoints + /// in the Authorization header, e.g.: Authorization: bearer {jwt} + /// + /// User's credentials (email and password) + /// JWT valid for 5 minutes. + /// Returned on request with valid credentials + /// Returned on request with missing form data (email, password or both) + /// Returned on request with unknown pair of email and password (wrong password) + /// Returned on request with unknwon email [HttpPost("login")] + [ProducesResponseType(200)] + [ProducesResponseType(typeof(ErrorDTO), 400)] + [ProducesResponseType(typeof(ErrorDTO), 401)] + [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task Login([FromBody] UserLoginDTO formUser) { // Ensure the form is complete @@ -52,8 +71,20 @@ public class AuthController : ControllerBase } // GET /api/v1/auth/some_values + /// + /// Dummy, authed endpoint + /// + /// + /// Dummy, authed endpoint used to test JWTs. + /// Authed endpoints expect Authorization header, e.g.: + /// Authorization: bearer {jwt} + /// Dummy json + /// Returned on request with valid credentials + /// Returned on request with invalid JWT [HttpGet("some_values")] [Authorize] + [ProducesResponseType(200)] + [ProducesResponseType(401)] public IActionResult GetValues() { return Ok(new string[] { "value1", "value2" }); diff --git a/Controllers/QuoteController.cs b/Controllers/QuoteController.cs index 60c28e2..99803ef 100644 --- a/Controllers/QuoteController.cs +++ b/Controllers/QuoteController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using QuotifyBE.Data; +using QuotifyBE.DTOs; using QuotifyBE.Entities; using QuotifyBE.Mapping; using System.Security.Claims; @@ -11,6 +12,7 @@ namespace QuotifyBE.Controllers; [ApiController] [Route("api/v1/quotes")] +[Produces("application/json")] public class QuotesController : ControllerBase { @@ -25,9 +27,16 @@ public class QuotesController : ControllerBase // GET /api/v1/quotes /// - /// Get a given quote page + /// Get a page of quotes /// + /// A page of quotes consists of 10 quotes or less. If a page does not contain any quotes, 404 is returned. + /// The page number + /// A page (10 quotes) + /// Returned on valid request + /// Returned when requested page is invalid or does not exist [HttpGet("page/{page_no}")] + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetQuotePage(int page_no) { // TODO... @@ -38,10 +47,19 @@ public class QuotesController : ControllerBase } // GET /api/v1/quotes/{id} + /// + /// Get specified quote summary + /// + /// The quote id in question + /// A quote: id, quote content and author, imageUrl and categories if successful, otherwise: error message + /// Returned on valid request + /// Returned when quote id is invalid or simply doesn't exist [HttpGet("{id}")] + [ProducesResponseType(typeof(QuoteShortDTO), 200)] + [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetQuoteById(int id) { - // FIXME: The expression 'q.QuoteCategories' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. + var quote = await _db.Quotes .Include(q => q.QuoteCategories!) .ThenInclude(qc => qc.Category) @@ -56,8 +74,19 @@ public class QuotesController : ControllerBase } // POST /api/v1/quotes/new + /// + /// Add a new quote + /// + /// Newly created quote's id + /// Form data containing required quote information + /// Returned on valid request + /// Returned when any of the categories does not exist + /// Returned when user's id does not match the creator's id [HttpPost("new")] [Authorize] + [ProducesResponseType(201)] // ? + [ProducesResponseType(typeof(ErrorDTO), 400)] + [ProducesResponseType(typeof(ErrorDTO), 401)] public async Task CreateQuote([FromBody] CreateQuoteDTO request) { // Get user ID from claims @@ -111,8 +140,16 @@ public class QuotesController : ControllerBase } // GET /api/v1/quotes/random + /// + /// Get a random quote summary + /// + /// A quote: id, quote content and author, imageUrl and categories if successful, otherwise: error message + /// Returned on valid request + /// Returned when no quotes exist [HttpGet("random")] [AllowAnonymous] + [ProducesResponseType(typeof(QuoteShortDTO), 200)] + [ProducesResponseType(typeof(ErrorDTO), 404)] public async Task GetRandomQuote() { var totalQuotes = await _db.Quotes.CountAsync(); diff --git a/DTOs/ErrorDTO.cs b/DTOs/ErrorDTO.cs new file mode 100644 index 0000000..f3065b6 --- /dev/null +++ b/DTOs/ErrorDTO.cs @@ -0,0 +1,7 @@ +namespace QuotifyBE.DTOs; + +public record class ErrorDTO +{ + public string Status { get; set; } + public string Error_msg { get; set; } +} diff --git a/DTOs/QuoteShortDTO.cs b/DTOs/QuoteShortDTO.cs index 6f57d05..a5b15e1 100644 --- a/DTOs/QuoteShortDTO.cs +++ b/DTOs/QuoteShortDTO.cs @@ -1,3 +1,5 @@ +namespace QuotifyBE.DTOs; + public record class QuoteShortDTO { public int Id { get; set; } diff --git a/Program.cs b/Program.cs index d8dc103..ce24c1f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,8 +1,10 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; using QuotifyBE.Controllers; using QuotifyBE.Data; +using System.Reflection; using System.Text; var builder = WebApplication.CreateBuilder(args); @@ -46,7 +48,29 @@ builder.Services.AddScoped(); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = "Quotify API", + Description = "An ASP.NET Core Web API for managing quotes", + Contact = new OpenApiContact + { + Name = "Quotify project's production branch", + Url = new Uri("https://quotify.7o7.cx") + }, + License = new OpenApiLicense + { + Name = "AGPLv3", + Url = new Uri("https://www.gnu.org/licenses/agpl-3.0.en.html") + } + }); + + // using System.Reflection; + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); +}); var app = builder.Build(); diff --git a/QuotifyBE.csproj b/QuotifyBE.csproj index c2e9c3d..b9e7634 100644 --- a/QuotifyBE.csproj +++ b/QuotifyBE.csproj @@ -7,6 +7,8 @@ b302b0ab-745f-4b53-b32a-12fbbc3e622d Linux . + true + $(NoWarn);1591 @@ -26,6 +28,7 @@ + diff --git a/WeatherForecast.cs b/WeatherForecast.cs deleted file mode 100644 index 242677c..0000000 --- a/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace QuotifyBE -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -}