diff --git a/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs index 656b96f6..9f701ed4 100644 --- a/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Lockers/GetAllLockersPaginatedQueryParameters.cs @@ -12,6 +12,10 @@ public class GetAllLockersPaginatedQueryParameters /// public Guid? RoomId { get; set; } /// + /// Search term + /// + public string? SearchTerm { get; set; } + /// /// Page number /// public int? Page { get; set; } diff --git a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs index 34721c39..89a7bbb6 100644 --- a/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs +++ b/src/Api/Controllers/Payload/Requests/Rooms/GetAllRoomsPaginatedQueryParameters.cs @@ -5,6 +5,10 @@ namespace Api.Controllers.Payload.Requests.Rooms; /// public class GetAllRoomsPaginatedQueryParameters { + /// + /// Search term + /// + public string? SearchTerm { get; set; } /// /// Page number /// diff --git a/src/Api/Controllers/RoomsController.cs b/src/Api/Controllers/RoomsController.cs index d46390cc..5ecb848d 100644 --- a/src/Api/Controllers/RoomsController.cs +++ b/src/Api/Controllers/RoomsController.cs @@ -36,14 +36,16 @@ public async Task>> GetById([FromRoute] Guid roomId /// /// Get all rooms paginated details /// A paginated list of rooms + [RequiresRole(IdentityData.Roles.Admin)] [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task>>> GetAllPaginated( - [FromQuery] GetAllLockersPaginatedQueryParameters queryParameters) + [FromQuery] GetAllRoomsPaginatedQueryParameters queryParameters) { var query = new GetAllRoomsPaginated.Query() { + SearchTerm = queryParameters.SearchTerm, Page = queryParameters.Page, Size = queryParameters.Size, SortBy = queryParameters.SortBy, diff --git a/src/Application/Common/Extensions/StringExtensions.cs b/src/Application/Common/Extensions/StringExtensions.cs new file mode 100644 index 00000000..b2a723de --- /dev/null +++ b/src/Application/Common/Extensions/StringExtensions.cs @@ -0,0 +1,13 @@ +namespace Application.Common.Extensions; + +public static class StringExtensions +{ + public static bool MatchesPropertyName(this string input) + where T : class + { + var type = typeof(T); + var properties = type.GetProperties(); + + return properties.Any(property => string.Equals(property.Name, input)); + } +} \ No newline at end of file diff --git a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs index 8094f631..aef45ae6 100644 --- a/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs +++ b/src/Application/Rooms/Queries/GetAllRoomsPaginated.cs @@ -1,5 +1,10 @@ +using Application.Common.Extensions; +using Application.Common.Interfaces; +using Application.Common.Mappings; using Application.Common.Models; using Application.Common.Models.Dtos.Physical; +using AutoMapper; +using AutoMapper.QueryableExtensions; using MediatR; namespace Application.Rooms.Queries; @@ -8,9 +13,49 @@ public class GetAllRoomsPaginated { public record Query : IRequest> { + public string? SearchTerm { get; init; } public int? Page { get; init; } public int? Size { get; init; } public string? SortBy { get; init; } public string? SortOrder { get; init; } } + + public class QueryHandler : IRequestHandler> + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public QueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task> Handle(Query request, CancellationToken cancellationToken) + { + var rooms = _context.Rooms.AsQueryable(); + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + rooms = rooms.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(RoomDto.Id); + } + var sortOrder = request.SortOrder ?? "asc"; + var pageNumber = request.Page is null or <= 0 ? 1 : request.Page; + var sizeNumber = request.Size is null or <= 0 ? 5 : request.Size; + + var result = await rooms + .ProjectTo(_mapper.ConfigurationProvider) + .OrderByCustom(sortBy, sortOrder) + .PaginatedListAsync(pageNumber.Value, sizeNumber.Value); + + return result; + } + } } \ No newline at end of file diff --git a/tests/Application.Tests.Integration/Rooms/Queries/GetAllRoomsPaginatedTests.cs b/tests/Application.Tests.Integration/Rooms/Queries/GetAllRoomsPaginatedTests.cs new file mode 100644 index 00000000..83c6c955 --- /dev/null +++ b/tests/Application.Tests.Integration/Rooms/Queries/GetAllRoomsPaginatedTests.cs @@ -0,0 +1,84 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Rooms.Queries; +using AutoMapper; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Rooms.Queries; + +public class GetAllRoomsPaginatedTests : BaseClassFixture +{ + private readonly IMapper _mapper; + public GetAllRoomsPaginatedTests(CustomApiFactory apiFactory) : base(apiFactory) + { + var configuration = new MapperConfiguration(config => config.AddProfile()); + + _mapper = configuration.CreateMapper(); + } + + [Fact] + public async Task ShouldReturnAllRooms() + { + // Arrange + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + var room1 = CreateRoom(department1); + var room2 = CreateRoom(department2); + await AddAsync(room1); + await AddAsync(room2); + + var query = new GetAllRoomsPaginated.Query(); + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(2); + result.Items.Should() + .ContainEquivalentOf(_mapper.Map(room1), + config => config.IgnoringCyclicReferences()); + result.Items.Should() + .ContainEquivalentOf(_mapper.Map(room2), + config => config.IgnoringCyclicReferences()); + + // Cleanup + Remove(room1); + Remove(room2); + Remove(department1); + Remove(department2); + } + + [Fact] + public async Task ShouldReturnOrderById_WhenWrongSortByIsProvided() + { + // Arrange + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + var room1 = CreateRoom(department1); + var room2 = CreateRoom(department2); + await AddAsync(room1); + await AddAsync(room2); + + var query = new GetAllRoomsPaginated.Query() + { + SortBy = "e", + }; + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(2); + result.Items.Should() + .BeEquivalentTo(_mapper.Map(new[] { room1, room2 }) + .OrderBy(x => x.Id), config => config.IgnoringCyclicReferences()); + result.Items.Should().BeInAscendingOrder(x => x.Id); + + // Cleanup + Remove(room1); + Remove(room2); + Remove(department1); + Remove(department2); + } +} \ No newline at end of file