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; }
- }
-}