From 826bb2e3952065a1ac645f55416791053ff8adbe Mon Sep 17 00:00:00 2001 From: zysim <9867871+zysim@users.noreply.github.com> Date: Thu, 19 Sep 2024 05:25:20 +0000 Subject: [PATCH] Get Leaderboard By Slug (#233) * Get leaderboard by slug * Use primary constructor syntax for all controllers * Update openapi.json * Exclude deleted boards in action * Switch to expression bodies --- .../Fixtures/IntegrationTestsBase.cs | 15 +++-------- LeaderboardBackend.Test/Leaderboards.cs | 25 ++++++++++++++++++- .../Controllers/AccountController.cs | 17 ++++--------- .../Controllers/CategoriesController.cs | 13 +++------- .../Controllers/UsersController.cs | 18 +++---------- .../Services/Impl/LeaderboardService.cs | 3 +-- 6 files changed, 39 insertions(+), 52 deletions(-) diff --git a/LeaderboardBackend.Test/Fixtures/IntegrationTestsBase.cs b/LeaderboardBackend.Test/Fixtures/IntegrationTestsBase.cs index 889d3c7e..bf138966 100644 --- a/LeaderboardBackend.Test/Fixtures/IntegrationTestsBase.cs +++ b/LeaderboardBackend.Test/Fixtures/IntegrationTestsBase.cs @@ -11,19 +11,10 @@ public abstract class IntegrationTestsBase protected static readonly TestApiFactory _factory = new(); [OneTimeSetUp] - public void OneTimeSetup() - { - _factory.InitializeDatabase(); - } + public void OneTimeSetup() => _factory.InitializeDatabase(); [SetUp] - public void SetUp() - { - Client = _factory.CreateClient(); - } + public void SetUp() => Client = _factory.CreateClient(); - protected void SetClientBearer(string token) - { - Client.DefaultRequestHeaders.Authorization = new(JwtBearerDefaults.AuthenticationScheme, token); - } + protected void SetClientBearer(string token) => Client.DefaultRequestHeaders.Authorization = new(JwtBearerDefaults.AuthenticationScheme, token); } diff --git a/LeaderboardBackend.Test/Leaderboards.cs b/LeaderboardBackend.Test/Leaderboards.cs index d6d58277..dad523eb 100644 --- a/LeaderboardBackend.Test/Leaderboards.cs +++ b/LeaderboardBackend.Test/Leaderboards.cs @@ -3,10 +3,13 @@ using System.Linq; using System.Net; using System.Threading.Tasks; +using LeaderboardBackend.Models.Entities; using LeaderboardBackend.Models.Requests; using LeaderboardBackend.Models.ViewModels; using LeaderboardBackend.Test.TestApi; using LeaderboardBackend.Test.TestApi.Extensions; +using Microsoft.Extensions.DependencyInjection; +using NodaTime; using NUnit.Framework; namespace LeaderboardBackend.Test; @@ -135,7 +138,27 @@ await _apiClient.Post( } CreateLeaderboardRequest reqForInexistentBoard = _createBoardReqFaker.Generate(); - Func> act = async () => await _apiClient.Get($"/api/leaderboard?slug={reqForInexistentBoard.Slug}", new()); + Func> act = async () => await _apiClient.Get($"/api/leaderboard?slug={reqForInexistentBoard.Slug}", new()); + await act.Should().ThrowAsync().Where(e => e.Response.StatusCode == HttpStatusCode.NotFound); + } + + [Test] + public async Task GetLeaderboards_Deleted_BySlug_NotFound() + { + ApplicationContext context = _factory.Services.CreateScope().ServiceProvider.GetRequiredService(); + Leaderboard board = new() + { + Name = "Should 404", + Slug = "should-404", + CreatedAt = Instant.FromUnixTimeSeconds(0), + UpdatedAt = Instant.FromUnixTimeSeconds(0), + DeletedAt = Instant.FromUnixTimeSeconds(0), + }; + + context.Leaderboards.Add(board); + await context.SaveChangesAsync(); + + Func> act = async () => await _apiClient.Get($"/api/leaderboard?slug={board.Slug}", new()); await act.Should().ThrowAsync().Where(e => e.Response.StatusCode == HttpStatusCode.NotFound); } diff --git a/LeaderboardBackend/Controllers/AccountController.cs b/LeaderboardBackend/Controllers/AccountController.cs index ddc3edae..8f42b65d 100644 --- a/LeaderboardBackend/Controllers/AccountController.cs +++ b/LeaderboardBackend/Controllers/AccountController.cs @@ -12,15 +12,8 @@ namespace LeaderboardBackend.Controllers; [Route("[controller]")] -public class AccountController : ApiController +public class AccountController(IUserService userService) : ApiController { - private readonly IUserService _userService; - - public AccountController(IUserService userService) - { - _userService = userService; - } - [AllowAnonymous] [FeatureGate(Features.ACCOUNT_REGISTRATION)] [HttpPost("register")] @@ -62,7 +55,7 @@ public async Task> Register( [FromServices] IAccountConfirmationService confirmationService ) { - CreateUserResult result = await _userService.CreateUser(request); + CreateUserResult result = await userService.CreateUser(request); if (result.TryPickT0(out User user, out CreateUserConflicts conflicts)) { @@ -126,7 +119,7 @@ public async Task> Login( )] LoginRequest request ) { - LoginResult result = await _userService.LoginByEmailAndPassword(request.Email, request.Password); + LoginResult result = await userService.LoginByEmailAndPassword(request.Email, request.Password); return result.Match>( loginToken => Ok(new LoginResponse { Token = loginToken }), @@ -149,7 +142,7 @@ [FromServices] IAccountConfirmationService confirmationService { // TODO: Handle rate limiting (429 case) - zysim - GetUserResult result = await _userService.GetUserFromClaims(HttpContext.User); + GetUserResult result = await userService.GetUserFromClaims(HttpContext.User); if (result.TryPickT0(out User user, out OneOf errors)) { @@ -180,7 +173,7 @@ public async Task RecoverAccount( [FromBody, SwaggerRequestBody("The account recovery request.")] RecoverAccountRequest request ) { - User? user = await _userService.GetUserByNameAndEmail(request.Username, request.Email); + User? user = await userService.GetUserByNameAndEmail(request.Username, request.Email); if (user is null) { diff --git a/LeaderboardBackend/Controllers/CategoriesController.cs b/LeaderboardBackend/Controllers/CategoriesController.cs index 7c64fd42..0b7be4c1 100644 --- a/LeaderboardBackend/Controllers/CategoriesController.cs +++ b/LeaderboardBackend/Controllers/CategoriesController.cs @@ -9,15 +9,8 @@ namespace LeaderboardBackend.Controllers; -public class CategoriesController : ApiController +public class CategoriesController(ICategoryService categoryService) : ApiController { - private readonly ICategoryService _categoryService; - - public CategoriesController(ICategoryService categoryService) - { - _categoryService = categoryService; - } - [AllowAnonymous] [HttpGet("api/category/{id}")] [SwaggerOperation("Gets a Category by its ID.", OperationId = "getCategory")] @@ -25,7 +18,7 @@ public CategoriesController(ICategoryService categoryService) [SwaggerResponse(404)] public async Task> GetCategory(long id) { - Category? category = await _categoryService.GetCategory(id); + Category? category = await categoryService.GetCategory(id); if (category == null) { @@ -54,7 +47,7 @@ [FromBody] CreateCategoryRequest request LeaderboardId = request.LeaderboardId, }; - await _categoryService.CreateCategory(category); + await categoryService.CreateCategory(category); return CreatedAtAction( nameof(GetCategory), diff --git a/LeaderboardBackend/Controllers/UsersController.cs b/LeaderboardBackend/Controllers/UsersController.cs index 20c57e5d..b42c6592 100644 --- a/LeaderboardBackend/Controllers/UsersController.cs +++ b/LeaderboardBackend/Controllers/UsersController.cs @@ -1,23 +1,14 @@ 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 +public class UsersController(IUserService userService) : ApiController { - private readonly IUserService _userService; - - public UsersController(IUserService userService) - { - _userService = userService; - } - [AllowAnonymous] [HttpGet("api/user/{id:guid}")] [SwaggerOperation("Gets a User by their ID.", OperationId = "getUser")] @@ -27,7 +18,7 @@ public async Task> GetUserById( [SwaggerParameter("The ID of the `User` which should be retrieved.")] Guid id ) { - User? user = await _userService.GetUserById(id); + User? user = await userService.GetUserById(id); if (user is null) { @@ -51,12 +42,9 @@ Call this method with the 'Authorization' header. A valid JWT bearer token must [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>( + public async Task> Me() => (await userService.GetUserFromClaims(HttpContext.User)).Match>( user => Ok(UserViewModel.MapFrom(user)), badCredentials => Unauthorized(), userNotFound => NotFound() ); - } } diff --git a/LeaderboardBackend/Services/Impl/LeaderboardService.cs b/LeaderboardBackend/Services/Impl/LeaderboardService.cs index 0f08e020..20678140 100644 --- a/LeaderboardBackend/Services/Impl/LeaderboardService.cs +++ b/LeaderboardBackend/Services/Impl/LeaderboardService.cs @@ -10,8 +10,7 @@ public class LeaderboardService(ApplicationContext applicationContext) : ILeader public async Task GetLeaderboardBySlug(string slug) => await applicationContext.Leaderboards - .AsNoTracking() - .FirstOrDefaultAsync(x => x.Slug == slug); + .FirstOrDefaultAsync(b => b.Slug == slug && b.DeletedAt == null); // FIXME: Paginate this public async Task> GetLeaderboards(long[]? ids = null)