diff --git a/LeaderboardBackend.Test/Categories.cs b/LeaderboardBackend.Test/Categories.cs index bb849d2a..2d34ecc8 100644 --- a/LeaderboardBackend.Test/Categories.cs +++ b/LeaderboardBackend.Test/Categories.cs @@ -32,16 +32,6 @@ public void OneTimeTearDown() _factory.Dispose(); } - [Test] - public static void GetCategory_Unauthorized() - { - RequestFailureException e = Assert.ThrowsAsync( - async () => await _apiClient.Get($"/api/categories/1", new()) - )!; - - Assert.AreEqual(HttpStatusCode.Unauthorized, e.Response.StatusCode); - } - [Test] public static void GetCategory_NotFound() { @@ -87,8 +77,7 @@ public static async Task CreateCategory_GetCategory_OK() ); CategoryViewModel retrievedCategory = await _apiClient.Get( - $"/api/categories/{createdCategory?.Id}", - new() { Jwt = _jwt } + $"/api/categories/{createdCategory?.Id}", new() { } ); Assert.AreEqual(createdCategory, retrievedCategory); diff --git a/LeaderboardBackend/Controllers/AccountController.cs b/LeaderboardBackend/Controllers/AccountController.cs index add32632..111809e7 100644 --- a/LeaderboardBackend/Controllers/AccountController.cs +++ b/LeaderboardBackend/Controllers/AccountController.cs @@ -1,4 +1,3 @@ -using LeaderboardBackend.Controllers.Annotations; using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; @@ -8,6 +7,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.FeatureManagement.Mvc; using OneOf; +using Swashbuckle.AspNetCore.Annotations; namespace LeaderboardBackend.Controllers; @@ -21,44 +21,44 @@ public AccountController(IUserService userService) _userService = userService; } - /// - /// Registers a new User. - /// - /// - /// The `RegisterRequest` instance from which register the `User`. - /// - /// The IConfirmationService dependency. - /// The `User` was registered and returned successfully. - /// - /// The request was malformed. - /// - /// - /// A `User` with the specified username or email already exists.

- /// Validation error codes by property: - /// - **Username**: - /// - **UsernameTaken**: the username is already in use - /// - **Email**: - /// - **EmailAlreadyUsed**: the email is already in use - ///
- /// - /// The request contains errors.

- /// Validation error codes by property: - /// - **Username**: - /// - **UsernameFormat**: Invalid username format - /// - **Password**: - /// - **PasswordFormat**: Invalid password format - /// - **Email**: - /// - **EmailValidator**: Invalid email format - ///
[AllowAnonymous] - [HttpPost("register")] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.PostAnon))] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(ValidationProblemDetails))] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] [FeatureGate(Features.ACCOUNT_REGISTRATION)] + [HttpPost("register")] + [SwaggerOperation("Registers a new User.")] + [SwaggerResponse(201, "The `User` was registered and returned successfully.")] + [SwaggerResponse( + 409, + """ + A `User` with the specified username or email already exists. + Validation error codes by property: + - **Username**: + - **UsernameTaken**: the username is already in use + - **Email**: + - **EmailAlreadyUsed**: the email is already in use + """, + typeof(ValidationProblemDetails) + )] + [SwaggerResponse( + 422, + """ + The request contains errors. + Validation error codes by property: + - **Username**: + - **UsernameFormat**: Invalid username format + - **Password**: + - **PasswordFormat**: Invalid password format + - **Email**: + - **EmailValidator**: Invalid email format + """, + typeof(ValidationProblemDetails) + )] public async Task> Register( - [FromBody] RegisterRequest request, + + [FromBody, SwaggerRequestBody( + "The `RegisterRequest` instance from which to register the `User`.", + Required = true + )] RegisterRequest request + , [FromServices] IAccountConfirmationService confirmationService ) { @@ -93,38 +93,38 @@ [FromServices] IAccountConfirmationService confirmationService return Conflict(new ValidationProblemDetails(ModelState)); } - /// - /// Logs a User in. - /// - /// - /// The `LoginRequest` instance from which to perform the login. - /// - /// - /// The `User` was logged in successfully. A `LoginResponse` is returned, containing a token. - /// - /// The request was malformed. - /// The password given was incorrect. - /// The associated `User` is banned. - /// No `User` with the requested details could be found. - /// - /// The request contains errors.

- /// Validation error codes by property: - /// - **Password**: - /// - **NotEmptyValidator**: No password was passed - /// - **PasswordFormat**: Invalid password format - /// - **Email**: - /// - **NotNullValidator**: No email was passed - /// - **EmailValidator**: Invalid email format - ///
[AllowAnonymous] - [HttpPost("/login")] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.PostAnon))] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] [FeatureGate(Features.LOGIN)] - public async Task> Login([FromBody] LoginRequest request) + [HttpPost("/login")] + [SwaggerOperation("Logs a User in.")] + [SwaggerResponse( + 200, + "The `User` was logged in successfully. A `LoginResponse` is returned, containing a token.", + typeof(LoginResponse) + )] + [SwaggerResponse(401, "The password given was incorrect.")] + [SwaggerResponse(403, "The associated `User` is banned.")] + [SwaggerResponse(404, "No `User` with the requested details could be found.")] + [SwaggerResponse( + 422, + """ + The request contains errors. + Validation error codes by property: + - **Password**: + - **NotEmptyValidator**: No password was passed + - **PasswordFormat**: Invalid password format + - **Email**: + - **NotNullValidator**: No email was passed + - **EmailValidator**: Invalid email format + """, + typeof(ValidationProblemDetails) + )] + public async Task> Login( + [FromBody, SwaggerRequestBody( + "The `LoginRequest` instance with which to perform the login.", + Required = true + )] LoginRequest request + ) { LoginResult result = await _userService.LoginByEmailAndPassword(request.Email, request.Password); @@ -136,30 +136,12 @@ public async Task> Login([FromBody] LoginRequest req ); } - /// - /// Resends the account confirmation link. - /// - /// IAccountConfirmationService dependency. - /// A new confirmation link was generated. - /// - /// The request was malformed. - /// - /// - /// The request doesn't contain a valid session token. - /// - /// - /// The `User`'s account has already been confirmed. - /// - /// - /// The account recovery email failed to be created. - /// [HttpPost("confirm")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - [ProducesResponseType(StatusCodes.Status429TooManyRequests)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [SwaggerOperation("Resends the account confirmation link.")] + [SwaggerResponse(200, "A new confirmation link was generated.")] + [SwaggerResponse(401)] + [SwaggerResponse(409, "The `User`'s account has already been confirmed.")] + [SwaggerResponse(500, "The account recovery email failed to be created.")] public async Task ResendConfirmation( [FromServices] IAccountConfirmationService confirmationService ) @@ -186,23 +168,15 @@ [FromServices] IAccountConfirmationService confirmationService ); } - /// - /// Sends an account recovery email. - /// - /// IAccountRecoveryService dependency. - /// - /// The account recovery request. - /// This endpoint returns 200 OK regardless of whether the email was sent successfully or not. - /// The request object was malformed. [AllowAnonymous] [HttpPost("recover")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + [SwaggerOperation("Sends an account recovery email.")] + [SwaggerResponse(200, "This endpoint returns 200 OK regardless of whether the email was sent successfully or not.")] [FeatureGate(Features.ACCOUNT_RECOVERY)] public async Task RecoverAccount( [FromServices] IAccountRecoveryService recoveryService, [FromServices] ILogger logger, - [FromBody] RecoverAccountRequest request + [FromBody, SwaggerRequestBody("The account recovery request.")] RecoverAccountRequest request ) { User? user = await _userService.GetUserByNameAndEmail(request.Username, request.Email); @@ -220,21 +194,16 @@ [FromBody] RecoverAccountRequest request return Ok(); } - /// - /// Confirms a user account. - /// - /// The confirmation token. - /// IAccountConfirmationService dependency. - /// The account was confirmed successfully. - /// The token provided was invalid or expired. - /// The user's account was either already confirmed or banned. [AllowAnonymous] [HttpPut("confirm/{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - public async Task ConfirmAccount(Guid id, [FromServices] IAccountConfirmationService confirmationService) + [SwaggerOperation("Confirms a user account.")] + [SwaggerResponse(200, "The account was confirmed successfully.")] + [SwaggerResponse(404, "The token provided was invalid or expired.")] + [SwaggerResponse(409, "the user's account was either already confirmed or banned.")] + public async Task ConfirmAccount( + [SwaggerParameter("The confirmation token.")] Guid id, + [FromServices] IAccountConfirmationService confirmationService + ) { ConfirmAccountResult result = await confirmationService.ConfirmAccount(id); @@ -247,20 +216,16 @@ public async Task ConfirmAccount(Guid id, [FromServices] IAccountC ); } - /// - /// Tests an account recovery token for validity. - /// - /// The recovery token. - /// IAccountRecoveryService dependency. - /// The token provided is valid. - /// The token provided is invalid or expired, or the user is banned. [AllowAnonymous] [HttpGet("recover/{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] + [SwaggerOperation("Tests an account recovery token for validity.")] + [SwaggerResponse(200, "The token provided is valid.")] + [SwaggerResponse(404, "The token provided is invalid or expired, or the user is banned.")] [FeatureGate(Features.ACCOUNT_RECOVERY)] - public async Task TestRecovery(Guid id, [FromServices] IAccountRecoveryService recoveryService) + public async Task TestRecovery( + [SwaggerParameter("The recovery token.")] Guid id, + [FromServices] IAccountRecoveryService recoveryService + ) { TestRecoveryResult result = await recoveryService.TestRecovery(id); @@ -273,32 +238,25 @@ public async Task TestRecovery(Guid id, [FromServices] IAccountRec ); } - /// - /// Recover the user's account by resetting their password to a new value. - /// - /// The recovery token. - /// The password recovery request object. - /// IAccountRecoveryService dependency - /// The user's password was reset successfully. - /// The user is banned. - /// The token provided is invalid or expired. - /// The new password is the same as the user's existing password. - /// - /// The request body contains errors.
- /// A **PasswordFormat** Validation error on the Password field indicates that the password format is invalid. - ///
[AllowAnonymous] - [HttpPost("recover/{id}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status409Conflict)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ValidationProblemDetails))] [FeatureGate(Features.ACCOUNT_RECOVERY)] + [HttpPost("recover/{id}")] + [SwaggerOperation("Recover the user's account by resetting their password to a new value.")] + [SwaggerResponse(200, "The user's password was reset successfully.")] + [SwaggerResponse(403, "The user is banned.")] + [SwaggerResponse(404, "The token provided is invalid or expired.")] + [SwaggerResponse(409, "The new password is the same as the user's existing password.")] + [SwaggerResponse( + 422, + """ + The request body contains errors. + A **PasswordFormat** Validation error on the Password field indicates that the password format is invalid. + """, + typeof(ValidationProblemDetails) + )] public async Task ResetPassword( - Guid id, - [FromBody] ChangePasswordRequest request, + [SwaggerParameter("The recovery token.")] Guid id, + [FromBody, SwaggerRequestBody("The password recovery request object.", Required = true)] ChangePasswordRequest request, [FromServices] IAccountRecoveryService recoveryService ) { diff --git a/LeaderboardBackend/Controllers/Annotations/Conventions.cs b/LeaderboardBackend/Controllers/Annotations/Conventions.cs deleted file mode 100644 index ec6f139c..00000000 --- a/LeaderboardBackend/Controllers/Annotations/Conventions.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApiExplorer; - -namespace LeaderboardBackend.Controllers.Annotations; - -public static class Conventions -{ - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public static void Get( - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] - [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] - object id, - params object[] parameters - ) - { } - - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public static void GetAnon( - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] - [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] - object id, - params object[] parameters - ) - { } - - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ValidationProblemDetails))] - public static void Post(params object[] parameters) { } - - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ValidationProblemDetails))] - public static void PostAnon(params object[] parameters) { } - - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity, Type = typeof(ValidationProblemDetails))] - public static void Update(params object[] parameters) { } - - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public static void Delete(params object[] parameters) { } -} diff --git a/LeaderboardBackend/Controllers/ApiController.cs b/LeaderboardBackend/Controllers/ApiController.cs index e4b33411..391e19f8 100644 --- a/LeaderboardBackend/Controllers/ApiController.cs +++ b/LeaderboardBackend/Controllers/ApiController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace LeaderboardBackend.Controllers; @@ -6,4 +7,6 @@ namespace LeaderboardBackend.Controllers; [Consumes("application/json")] [Produces("application/json")] [Route("api/[controller]")] +[SwaggerResponse(400, Type = typeof(ProblemDetails))] +[SwaggerResponse(500)] public abstract class ApiController : ControllerBase { } diff --git a/LeaderboardBackend/Controllers/CategoriesController.cs b/LeaderboardBackend/Controllers/CategoriesController.cs index 157bc46f..7f7e5716 100644 --- a/LeaderboardBackend/Controllers/CategoriesController.cs +++ b/LeaderboardBackend/Controllers/CategoriesController.cs @@ -1,9 +1,10 @@ -using LeaderboardBackend.Controllers.Annotations; using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; using LeaderboardBackend.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace LeaderboardBackend.Controllers; @@ -16,14 +17,11 @@ public CategoriesController(ICategoryService categoryService) _categoryService = categoryService; } - /// - /// Gets a Category by its ID. - /// - /// The ID of the `Category` which should be retrieved. - /// The `Category` was found and returned successfully. - /// No `Category` with the requested ID could be found. - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Get))] + [AllowAnonymous] [HttpGet("{id}")] + [SwaggerOperation("Gets a Category by its ID.")] + [SwaggerResponse(200)] + [SwaggerResponse(404)] public async Task> GetCategory(long id) { // NOTE: Should this use [AllowAnonymous]? - Ero @@ -38,20 +36,11 @@ public async Task> GetCategory(long id) return Ok(CategoryViewModel.MapFrom(category)); } - /// - /// Creates a new Category. - /// This request is restricted to Moderators. - /// - /// - /// The `CreateCategoryRequest` instance from which to create the `Category`. - /// - /// The `Category` was created and returned successfully. - /// The request was malformed. - /// - /// The requesting `User` is unauthorized to create a `Category`. - /// - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Post))] [HttpPost] + [SwaggerOperation("Creates a new Category. This request is restricted to Moderators.")] + [SwaggerResponse(201)] + [SwaggerResponse(404)] + [SwaggerResponse(422, Type = typeof(ValidationProblemDetails))] public async Task> CreateCategory( [FromBody] CreateCategoryRequest request ) diff --git a/LeaderboardBackend/Controllers/LeaderboardsController.cs b/LeaderboardBackend/Controllers/LeaderboardsController.cs index f1008349..2641277e 100644 --- a/LeaderboardBackend/Controllers/LeaderboardsController.cs +++ b/LeaderboardBackend/Controllers/LeaderboardsController.cs @@ -1,11 +1,11 @@ using LeaderboardBackend.Authorization; -using LeaderboardBackend.Controllers.Annotations; using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; using LeaderboardBackend.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace LeaderboardBackend.Controllers; @@ -18,15 +18,11 @@ public LeaderboardsController(ILeaderboardService leaderboardService) _leaderboardService = leaderboardService; } - /// - /// Gets a Leaderboard by its ID. - /// - /// The ID of the `Leaderboard` which should be retrieved. - /// The `Leaderboard` was found and returned successfully. - /// No `Leaderboard` with the requested ID or slug could be found. [AllowAnonymous] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.GetAnon))] [HttpGet("{id:long}")] + [SwaggerOperation("Gets a leaderboard by its ID.")] + [SwaggerResponse(200)] + [SwaggerResponse(404)] public async Task> GetLeaderboard(long id) { Leaderboard? leaderboard = await _leaderboardService.GetLeaderboard(id); @@ -39,15 +35,11 @@ public async Task> GetLeaderboard(long id) return Ok(LeaderboardViewModel.MapFrom(leaderboard)); } - /// - /// Gets a Leaderboard by its slug. - /// - /// The slug of the `Leaderboard` which should be retrieved. - /// The `Leaderboard` was found and returned successfully. - /// No `Leaderboard` with the requested ID or slug could be found. [AllowAnonymous] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.GetAnon))] [HttpGet("{slug}")] + [SwaggerOperation("Gets a Leaderboard by its slug.")] + [SwaggerResponse(200)] + [SwaggerResponse(404)] public async Task> GetLeaderboardBySlug(string slug) { Leaderboard? leaderboard = await _leaderboardService.GetLeaderboardBySlug(slug); @@ -60,17 +52,10 @@ public async Task> GetLeaderboardBySlug(strin return Ok(LeaderboardViewModel.MapFrom(leaderboard)); } - /// - /// Gets Leaderboards by their IDs. - /// - /// The IDs of the `Leaderboard`s which should be retrieved. - /// - /// The list of `Leaderboard`s was retrieved successfully. The result can be an empty - /// collection. - /// [AllowAnonymous] [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] + [SwaggerOperation("Gets leaderboards by their IDs.")] + [SwaggerResponse(200)] public async Task>> GetLeaderboards( [FromQuery] long[] ids ) @@ -79,21 +64,13 @@ [FromQuery] long[] ids return Ok(result.Select(LeaderboardViewModel.MapFrom)); } - /// - /// Creates a new Leaderboard. - /// This request is restricted to Administrators. - /// - /// - /// The `CreateLeaderboardRequest` instance from which to create the `Leaderboard`. - /// - /// The `Leaderboard` was created and returned successfully. - /// The request was malformed. - /// - /// The requesting `User` is unauthorized to create `Leaderboard`s. - /// - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Post))] [Authorize(Policy = UserTypes.ADMINISTRATOR)] [HttpPost] + [SwaggerOperation("Creates a new leaderboard. This request is restricted to Administrators.")] + [SwaggerResponse(201)] + [SwaggerResponse(401)] + [SwaggerResponse(404, "The requesting `User` is unauthorized to create `Leaderboard`s.")] + [SwaggerResponse(422, Type = typeof(ValidationProblemDetails))] public async Task> CreateLeaderboard( [FromBody] CreateLeaderboardRequest request ) diff --git a/LeaderboardBackend/Controllers/RunsController.cs b/LeaderboardBackend/Controllers/RunsController.cs index a72b66ad..8270f6a7 100644 --- a/LeaderboardBackend/Controllers/RunsController.cs +++ b/LeaderboardBackend/Controllers/RunsController.cs @@ -1,9 +1,10 @@ -using LeaderboardBackend.Controllers.Annotations; using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; using LeaderboardBackend.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; namespace LeaderboardBackend.Controllers; @@ -21,21 +22,13 @@ ICategoryService categoryService _categoryService = categoryService; } - /// - /// Gets a Run by its ID. - /// - /// - /// The ID of the `Run` which should be retrieved.
- /// It must be possible to parse this to `long` for this request to complete. - /// - /// The `Run` was found and returned successfully. - /// No `Run` with the requested ID could be found. - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Get))] + [AllowAnonymous] [HttpGet("{id}")] + [SwaggerOperation("Gets a Run by its ID.")] + [SwaggerResponse(200)] + [SwaggerResponse(404)] public async Task> GetRun(Guid id) { - // NOTE: Should this use [AllowAnonymous]? - Ero - Run? run = await _runService.GetRun(id); if (run is null) @@ -46,15 +39,12 @@ public async Task> GetRun(Guid id) return Ok(RunViewModel.MapFrom(run)); } - /// - /// Creates a new Run. - /// - /// - /// The `CreateRunRequest` instance from which to create the `Run`. - /// - /// The `Run` was created and returned successfully. - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Post))] [HttpPost] + [SwaggerOperation("Creates a new Run.")] + [SwaggerResponse(201)] + [SwaggerResponse(401)] + [SwaggerResponse(403)] + [SwaggerResponse(422, Type = typeof(ValidationProblemDetails))] public async Task CreateRun([FromBody] CreateRunRequest request) { // FIXME: Should return Task>! - Ero @@ -73,8 +63,9 @@ public async Task CreateRun([FromBody] CreateRunRequest request) return CreatedAtAction(nameof(GetRun), new { id = run.Id }, RunViewModel.MapFrom(run)); } - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Get))] [HttpGet("{id}/category")] + [SwaggerResponse(200)] + [SwaggerResponse(404)] public async Task> GetCategoryForRun(Guid id) { Run? run = await _runService.GetRun(id); diff --git a/LeaderboardBackend/Controllers/UsersController.cs b/LeaderboardBackend/Controllers/UsersController.cs index 47baa4b5..9f126718 100644 --- a/LeaderboardBackend/Controllers/UsersController.cs +++ b/LeaderboardBackend/Controllers/UsersController.cs @@ -1,35 +1,31 @@ -using LeaderboardBackend.Controllers.Annotations; using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; using LeaderboardBackend.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; using BCryptNet = BCrypt.Net.BCrypt; namespace LeaderboardBackend.Controllers; public class UsersController : ApiController { - private readonly IAuthService _authService; private readonly IUserService _userService; - public UsersController(IAuthService authService, IUserService userService) + public UsersController(IUserService userService) { - _authService = authService; _userService = userService; } - /// - /// Gets a User by their ID. - /// - /// The ID of the `User` which should be retrieved. - /// The `User` was found and returned successfully. - /// No `User` with the requested ID could be found. [AllowAnonymous] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.GetAnon))] [HttpGet("{id:guid}")] - public async Task> GetUserById(Guid id) + [SwaggerOperation("Gets a User by their ID.")] + [SwaggerResponse(200, "The `User` was found and returned successfully.")] + [SwaggerResponse(404, "No `User` with the requested ID could be found.")] + public async Task> GetUserById( + [SwaggerParameter("The ID of the `User` which should be retrieved.")] Guid id + ) { User? user = await _userService.GetUserById(id); @@ -41,22 +37,18 @@ public async Task> GetUserById(Guid id) return Ok(UserViewModel.MapFrom(user)); } - /// - /// Gets the currently logged-in User. - /// - /// - /// Call this method with the 'Authorization' header. A valid JWT bearer token must be - /// passed.
- /// Example: `{ 'Authorization': 'Bearer JWT' }`. - ///
- /// The `User` was found and returned successfully.. - /// An invalid JWT was passed in. - /// The user was not found in the database. [HttpGet("me")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ApiConventionMethod(typeof(Conventions), nameof(Conventions.Get))] + [SwaggerOperation( + "Gets the currently logged-in User.", + """ + Call this method with the 'Authorization' header. A valid JWT bearer token must be + passed. + Example: `{ 'Authorization': 'Bearer JWT' }`. + """ + )] + [SwaggerResponse(200, "The `User` was found and returned successfully.")] + [SwaggerResponse(401, "An invalid JWT was passed in.")] + [SwaggerResponse(404, "The user was not found in the database.")] public async Task> Me() { return (await _userService.GetUserFromClaims(HttpContext.User)).Match>( diff --git a/LeaderboardBackend/LeaderboardBackend.csproj b/LeaderboardBackend/LeaderboardBackend.csproj index c9f65b4b..f0ba5a90 100644 --- a/LeaderboardBackend/LeaderboardBackend.csproj +++ b/LeaderboardBackend/LeaderboardBackend.csproj @@ -44,6 +44,7 @@ + diff --git a/LeaderboardBackend/Program.cs b/LeaderboardBackend/Program.cs index 84d1ddb8..e532b375 100644 --- a/LeaderboardBackend/Program.cs +++ b/LeaderboardBackend/Program.cs @@ -157,6 +157,7 @@ // Enable adding XML comments to controllers to populate Swagger UI string xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + c.EnableAnnotations(); c.AddSecurityDefinition( "Bearer", diff --git a/LeaderboardBackend/openapi.json b/LeaderboardBackend/openapi.json index 5b4f9b6a..d9645cac 100644 --- a/LeaderboardBackend/openapi.json +++ b/LeaderboardBackend/openapi.json @@ -12,16 +12,30 @@ ], "summary": "Registers a new User.", "requestBody": { - "description": "The `RegisterRequest` instance from which register the `User`.", + "description": "The `RegisterRequest` instance from which to register the `User`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterRequest" } } - } + }, + "required": true }, "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "500": { + "description": "Server Error" + }, "201": { "description": "The `User` was registered and returned successfully.", "content": { @@ -33,7 +47,7 @@ } }, "409": { - "description": "A `User` with the specified username or email already exists.

\r\nValidation error codes by property:\r\n- **Username**:\r\n - **UsernameTaken**: the username is already in use\r\n- **Email**:\r\n - **EmailAlreadyUsed**: the email is already in use", + "description": "A `User` with the specified username or email already exists.\nValidation error codes by property:\n- **Username**:\n - **UsernameTaken**: the username is already in use\n- **Email**:\n - **EmailAlreadyUsed**: the email is already in use", "content": { "application/json": { "schema": { @@ -42,14 +56,15 @@ } } }, - "500": { - "description": "Server Error" - }, - "400": { - "description": "The request was malformed." - }, "422": { - "description": "The request contains errors.

\r\nValidation error codes by property:\r\n- **Username**:\r\n - **UsernameFormat**: Invalid username format\r\n- **Password**:\r\n - **PasswordFormat**: Invalid password format\r\n- **Email**:\r\n - **EmailValidator**: Invalid email format" + "description": "The request contains errors.\nValidation error codes by property:\n- **Username**:\n - **UsernameFormat**: Invalid username format\n- **Password**:\n - **PasswordFormat**: Invalid password format\n- **Email**:\n - **EmailValidator**: Invalid email format", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationProblemDetails" + } + } + } } } } @@ -61,61 +76,58 @@ ], "summary": "Logs a User in.", "requestBody": { - "description": "The `LoginRequest` instance from which to perform the login.", + "description": "The `LoginRequest` instance with which to perform the login.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } - } + }, + "required": true }, "responses": { - "200": { - "description": "The `User` was logged in successfully. A `LoginResponse` is returned, containing a token.", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/LoginResponse" + "$ref": "#/components/schemas/ProblemDetails" } } } }, - "401": { - "description": "The password given was incorrect.", + "500": { + "description": "Server Error" + }, + "200": { + "description": "The `User` was logged in successfully. A `LoginResponse` is returned, containing a token.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/LoginResponse" } } } }, + "401": { + "description": "The password given was incorrect." + }, "403": { - "description": "The associated `User` is banned.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The associated `User` is banned." }, "404": { - "description": "No `User` with the requested details could be found.", + "description": "No `User` with the requested details could be found." + }, + "422": { + "description": "The request contains errors.\nValidation error codes by property:\n- **Password**:\n - **NotEmptyValidator**: No password was passed\n - **PasswordFormat**: Invalid password format\n- **Email**:\n - **NotNullValidator**: No email was passed\n - **EmailValidator**: Invalid email format", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/ValidationProblemDetails" } } } - }, - "400": { - "description": "The request was malformed." - }, - "422": { - "description": "The request contains errors.

\r\nValidation error codes by property:\r\n- **Password**:\r\n - **NotEmptyValidator**: No password was passed\r\n - **PasswordFormat**: Invalid password format\r\n- **Email**:\r\n - **NotNullValidator**: No email was passed\r\n - **EmailValidator**: Invalid email format" } } } @@ -127,11 +139,8 @@ ], "summary": "Resends the account confirmation link.", "responses": { - "200": { - "description": "A new confirmation link was generated." - }, "400": { - "description": "The request was malformed.", + "description": "Bad Request", "content": { "application/json": { "schema": { @@ -140,38 +149,17 @@ } } }, - "401": { - "description": "The request doesn't contain a valid session token.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "The account recovery email failed to be created." }, - "409": { - "description": "The `User`'s account has already been confirmed.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "200": { + "description": "A new confirmation link was generated." }, - "429": { - "description": "Too Many Requests", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "401": { + "description": "Unauthorized" }, - "500": { - "description": "The account recovery email failed to be created." + "409": { + "description": "The `User`'s account has already been confirmed." } } } @@ -193,11 +181,8 @@ } }, "responses": { - "200": { - "description": "This endpoint returns 200 OK regardless of whether the email was sent successfully or not." - }, "400": { - "description": "The request object was malformed.", + "description": "Bad Request", "content": { "application/json": { "schema": { @@ -205,6 +190,12 @@ } } } + }, + "500": { + "description": "Server Error" + }, + "200": { + "description": "This endpoint returns 200 OK regardless of whether the email was sent successfully or not." } } } @@ -228,9 +219,6 @@ } ], "responses": { - "200": { - "description": "The account was confirmed successfully." - }, "400": { "description": "Bad Request", "content": { @@ -241,25 +229,17 @@ } } }, + "500": { + "description": "Server Error" + }, + "200": { + "description": "The account was confirmed successfully." + }, "404": { - "description": "The token provided was invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The token provided was invalid or expired." }, "409": { - "description": "The user's account was either already confirmed or banned.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "the user's account was either already confirmed or banned." } } } @@ -283,9 +263,6 @@ } ], "responses": { - "200": { - "description": "The token provided is valid." - }, "400": { "description": "Bad Request", "content": { @@ -296,15 +273,14 @@ } } }, + "500": { + "description": "Server Error" + }, + "200": { + "description": "The token provided is valid." + }, "404": { - "description": "The token provided is invalid or expired, or the user is banned.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The token provided is invalid or expired, or the user is banned." } } }, @@ -333,12 +309,10 @@ "$ref": "#/components/schemas/ChangePasswordRequest" } } - } + }, + "required": true }, "responses": { - "200": { - "description": "The user's password was reset successfully." - }, "400": { "description": "Bad Request", "content": { @@ -349,38 +323,23 @@ } } }, + "500": { + "description": "Server Error" + }, + "200": { + "description": "The user's password was reset successfully." + }, "403": { - "description": "The user is banned.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The user is banned." }, "404": { - "description": "The token provided is invalid or expired.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The token provided is invalid or expired." }, "409": { - "description": "The new password is the same as the user's existing password.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The new password is the same as the user's existing password." }, "422": { - "description": "The request body contains errors.
\r\nA **PasswordFormat** Validation error on the Password field indicates that the password format is invalid.", + "description": "The request body contains errors.\nA **PasswordFormat** Validation error on the Password field indicates that the password format is invalid.", "content": { "application/json": { "schema": { @@ -402,7 +361,6 @@ { "name": "id", "in": "path", - "description": "The ID of the `Category` which should be retrieved.", "required": true, "schema": { "type": "integer", @@ -411,16 +369,6 @@ } ], "responses": { - "200": { - "description": "The `Category` was found and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -431,35 +379,21 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "Server Error" }, - "403": { - "description": "Forbidden", + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/CategoryViewModel" } } } }, "404": { - "description": "No `Category` with the requested ID could be found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Not Found" } } } @@ -469,9 +403,8 @@ "tags": [ "Categories" ], - "summary": "Creates a new Category.\r\nThis request is restricted to Moderators.", + "summary": "Creates a new Category. This request is restricted to Moderators.", "requestBody": { - "description": "The `CreateCategoryRequest` instance from which to create the `Category`.", "content": { "application/json": { "schema": { @@ -481,18 +414,8 @@ } }, "responses": { - "201": { - "description": "The `Category` was created and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryViewModel" - } - } - } - }, "400": { - "description": "The request was malformed.", + "description": "Bad Request", "content": { "application/json": { "schema": { @@ -501,35 +424,21 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "Server Error" }, - "403": { - "description": "Forbidden", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/CategoryViewModel" } } } }, "404": { - "description": "The requesting `User` is unauthorized to create a `Category`.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Not Found" }, "422": { "description": "Client Error", @@ -549,12 +458,11 @@ "tags": [ "Leaderboards" ], - "summary": "Gets a Leaderboard by its ID.", + "summary": "Gets a leaderboard by its ID.", "parameters": [ { "name": "id", "in": "path", - "description": "The ID of the `Leaderboard` which should be retrieved.", "required": true, "schema": { "type": "integer", @@ -563,16 +471,6 @@ } ], "responses": { - "200": { - "description": "The `Leaderboard` was found and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LeaderboardViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -583,15 +481,21 @@ } } }, - "404": { - "description": "No `Leaderboard` with the requested ID or slug could be found.", + "500": { + "description": "Server Error" + }, + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/LeaderboardViewModel" } } } + }, + "404": { + "description": "Not Found" } } } @@ -606,7 +510,6 @@ { "name": "slug", "in": "path", - "description": "The slug of the `Leaderboard` which should be retrieved.", "required": true, "schema": { "type": "string" @@ -614,16 +517,6 @@ } ], "responses": { - "200": { - "description": "The `Leaderboard` was found and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LeaderboardViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -634,15 +527,21 @@ } } }, - "404": { - "description": "No `Leaderboard` with the requested ID or slug could be found.", + "500": { + "description": "Server Error" + }, + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/LeaderboardViewModel" } } } + }, + "404": { + "description": "Not Found" } } } @@ -652,12 +551,11 @@ "tags": [ "Leaderboards" ], - "summary": "Gets Leaderboards by their IDs.", + "summary": "Gets leaderboards by their IDs.", "parameters": [ { "name": "ids", "in": "query", - "description": "The IDs of the `Leaderboard`s which should be retrieved.", "schema": { "type": "array", "items": { @@ -668,8 +566,21 @@ } ], "responses": { + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "500": { + "description": "Server Error" + }, "200": { - "description": "The list of `Leaderboard`s was retrieved successfully. The result can be an empty\r\ncollection.", + "description": "Success", "content": { "application/json": { "schema": { @@ -687,9 +598,8 @@ "tags": [ "Leaderboards" ], - "summary": "Creates a new Leaderboard.\r\nThis request is restricted to Administrators.", + "summary": "Creates a new leaderboard. This request is restricted to Administrators.", "requestBody": { - "description": "The `CreateLeaderboardRequest` instance from which to create the `Leaderboard`.", "content": { "application/json": { "schema": { @@ -699,18 +609,8 @@ } }, "responses": { - "201": { - "description": "The `Leaderboard` was created and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LeaderboardViewModel" - } - } - } - }, "400": { - "description": "The request was malformed.", + "description": "Bad Request", "content": { "application/json": { "schema": { @@ -719,35 +619,24 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "Server Error" }, - "403": { - "description": "Forbidden", + "201": { + "description": "Created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/LeaderboardViewModel" } } } }, + "401": { + "description": "Unauthorized" + }, "404": { - "description": "The requesting `User` is unauthorized to create `Leaderboard`s.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The requesting `User` is unauthorized to create `Leaderboard`s." }, "422": { "description": "Client Error", @@ -772,7 +661,6 @@ { "name": "id", "in": "path", - "description": "The ID of the `Run` which should be retrieved.
\r\nIt must be possible to parse this to `long` for this request to complete.", "required": true, "schema": { "pattern": "^[a-zA-Z0-9-_]{22}$", @@ -781,16 +669,6 @@ } ], "responses": { - "200": { - "description": "The `Run` was found and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RunViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -801,35 +679,21 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "Server Error" }, - "403": { - "description": "Forbidden", + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/RunViewModel" } } } }, "404": { - "description": "No `Run` with the requested ID could be found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Not Found" } } } @@ -841,7 +705,6 @@ ], "summary": "Creates a new Run.", "requestBody": { - "description": "The `CreateRunRequest` instance from which to create the `Run`.", "content": { "application/json": { "schema": { @@ -851,9 +714,6 @@ } }, "responses": { - "201": { - "description": "The `Run` was created and returned successfully." - }, "400": { "description": "Bad Request", "content": { @@ -864,35 +724,17 @@ } } }, + "500": { + "description": "Server Error" + }, + "201": { + "description": "Created" + }, "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Unauthorized" }, "403": { - "description": "Forbidden", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } - }, - "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Forbidden" }, "422": { "description": "Client Error", @@ -924,16 +766,6 @@ } ], "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CategoryViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -944,35 +776,21 @@ } } }, - "401": { - "description": "Unauthorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "500": { + "description": "Server Error" }, - "403": { - "description": "Forbidden", + "200": { + "description": "Success", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/CategoryViewModel" } } } }, "404": { - "description": "Not Found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "Not Found" } } } @@ -996,16 +814,6 @@ } ], "responses": { - "200": { - "description": "The `User` was found and returned successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserViewModel" - } - } - } - }, "400": { "description": "Bad Request", "content": { @@ -1016,15 +824,21 @@ } } }, - "404": { - "description": "No `User` with the requested ID could be found.", + "500": { + "description": "Server Error" + }, + "200": { + "description": "The `User` was found and returned successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/UserViewModel" } } } + }, + "404": { + "description": "No `User` with the requested ID could be found." } } } @@ -1035,37 +849,36 @@ "Users" ], "summary": "Gets the currently logged-in User.", - "description": "Call this method with the 'Authorization' header. A valid JWT bearer token must be\r\npassed.
\r\nExample: `{ 'Authorization': 'Bearer JWT' }`.", + "description": "Call this method with the 'Authorization' header. A valid JWT bearer token must be\npassed.\nExample: `{ 'Authorization': 'Bearer JWT' }`.", "responses": { - "200": { - "description": "The `User` was found and returned successfully..", + "400": { + "description": "Bad Request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserViewModel" + "$ref": "#/components/schemas/ProblemDetails" } } } }, - "401": { - "description": "An invalid JWT was passed in.", + "500": { + "description": "Server Error" + }, + "200": { + "description": "The `User` was found and returned successfully.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ProblemDetails" + "$ref": "#/components/schemas/UserViewModel" } } } }, + "401": { + "description": "An invalid JWT was passed in." + }, "404": { - "description": "The user was not found in the database.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProblemDetails" - } - } - } + "description": "The user was not found in the database." } } }