Skip to content

Commit

Permalink
Get Leaderboard By Slug (#233)
Browse files Browse the repository at this point in the history
* Get leaderboard by slug

* Use primary constructor syntax for all controllers

* Update openapi.json

* Exclude deleted boards in action

* Switch to expression bodies
  • Loading branch information
zysim committed Sep 19, 2024
1 parent 4c5bedf commit 826bb2e
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 52 deletions.
15 changes: 3 additions & 12 deletions LeaderboardBackend.Test/Fixtures/IntegrationTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
25 changes: 24 additions & 1 deletion LeaderboardBackend.Test/Leaderboards.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -135,7 +138,27 @@ await _apiClient.Post<LeaderboardViewModel>(
}

CreateLeaderboardRequest reqForInexistentBoard = _createBoardReqFaker.Generate();
Func<Task<string>> act = async () => await _apiClient.Get<string>($"/api/leaderboard?slug={reqForInexistentBoard.Slug}", new());
Func<Task<LeaderboardViewModel>> act = async () => await _apiClient.Get<LeaderboardViewModel>($"/api/leaderboard?slug={reqForInexistentBoard.Slug}", new());
await act.Should().ThrowAsync<RequestFailureException>().Where(e => e.Response.StatusCode == HttpStatusCode.NotFound);
}

[Test]
public async Task GetLeaderboards_Deleted_BySlug_NotFound()
{
ApplicationContext context = _factory.Services.CreateScope().ServiceProvider.GetRequiredService<ApplicationContext>();
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<Task<LeaderboardViewModel>> act = async () => await _apiClient.Get<LeaderboardViewModel>($"/api/leaderboard?slug={board.Slug}", new());
await act.Should().ThrowAsync<RequestFailureException>().Where(e => e.Response.StatusCode == HttpStatusCode.NotFound);
}

Expand Down
17 changes: 5 additions & 12 deletions LeaderboardBackend/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -62,7 +55,7 @@ public async Task<ActionResult<UserViewModel>> 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))
{
Expand Down Expand Up @@ -126,7 +119,7 @@ public async Task<ActionResult<LoginResponse>> Login(
)] LoginRequest request
)
{
LoginResult result = await _userService.LoginByEmailAndPassword(request.Email, request.Password);
LoginResult result = await userService.LoginByEmailAndPassword(request.Email, request.Password);

return result.Match<ActionResult<LoginResponse>>(
loginToken => Ok(new LoginResponse { Token = loginToken }),
Expand All @@ -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<BadCredentials, UserNotFound> errors))
{
Expand Down Expand Up @@ -180,7 +173,7 @@ public async Task<ActionResult> 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)
{
Expand Down
13 changes: 3 additions & 10 deletions LeaderboardBackend/Controllers/CategoriesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,16 @@

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")]
[SwaggerResponse(200)]
[SwaggerResponse(404)]
public async Task<ActionResult<CategoryViewModel>> GetCategory(long id)
{
Category? category = await _categoryService.GetCategory(id);
Category? category = await categoryService.GetCategory(id);

if (category == null)
{
Expand Down Expand Up @@ -54,7 +47,7 @@ [FromBody] CreateCategoryRequest request
LeaderboardId = request.LeaderboardId,
};

await _categoryService.CreateCategory(category);
await categoryService.CreateCategory(category);

return CreatedAtAction(
nameof(GetCategory),
Expand Down
18 changes: 3 additions & 15 deletions LeaderboardBackend/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -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")]
Expand All @@ -27,7 +18,7 @@ public async Task<ActionResult<UserViewModel>> 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)
{
Expand All @@ -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<ActionResult<UserViewModel>> Me()
{
return (await _userService.GetUserFromClaims(HttpContext.User)).Match<ActionResult<UserViewModel>>(
public async Task<ActionResult<UserViewModel>> Me() => (await userService.GetUserFromClaims(HttpContext.User)).Match<ActionResult<UserViewModel>>(
user => Ok(UserViewModel.MapFrom(user)),
badCredentials => Unauthorized(),
userNotFound => NotFound()
);
}
}
3 changes: 1 addition & 2 deletions LeaderboardBackend/Services/Impl/LeaderboardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ public class LeaderboardService(ApplicationContext applicationContext) : ILeader

public async Task<Leaderboard?> 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<List<Leaderboard>> GetLeaderboards(long[]? ids = null)
Expand Down

0 comments on commit 826bb2e

Please sign in to comment.