diff --git a/src/Api/Controllers/LockersController.cs b/src/Api/Controllers/LockersController.cs index 373a0cee..2445df95 100644 --- a/src/Api/Controllers/LockersController.cs +++ b/src/Api/Controllers/LockersController.cs @@ -44,6 +44,7 @@ public async Task>>> GetAllPaginate var query = new GetAllLockersPaginated.Query() { RoomId = queryParameters.RoomId, + SearchTerm = queryParameters.SearchTerm, Page = queryParameters.Page, Size = queryParameters.Size, SortBy = queryParameters.SortBy, 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/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/Lockers/Queries/GetAllLockersPaginated.cs b/src/Application/Lockers/Queries/GetAllLockersPaginated.cs index f7935335..794764b5 100644 --- a/src/Application/Lockers/Queries/GetAllLockersPaginated.cs +++ b/src/Application/Lockers/Queries/GetAllLockersPaginated.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.Lockers.Queries; @@ -9,9 +14,54 @@ public class GetAllLockersPaginated public record Query : IRequest> { public Guid? RoomId { get; init; } + 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 lockers = _context.Lockers.AsQueryable(); + + if (request.RoomId is not null) + { + lockers = lockers.Where(x => x.Room.Id == request.RoomId); + } + + if (!(request.SearchTerm is null || request.SearchTerm.Trim().Equals(string.Empty))) + { + lockers = lockers.Where(x => + x.Name.ToLower().Contains(request.SearchTerm.ToLower())); + } + + var sortBy = request.SortBy; + if (sortBy is null || !sortBy.MatchesPropertyName()) + { + sortBy = nameof(LockerDto.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 lockers + .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/Lockers/Queries/GetAllLockersPaginatedTests.cs b/tests/Application.Tests.Integration/Lockers/Queries/GetAllLockersPaginatedTests.cs new file mode 100644 index 00000000..6693ea51 --- /dev/null +++ b/tests/Application.Tests.Integration/Lockers/Queries/GetAllLockersPaginatedTests.cs @@ -0,0 +1,89 @@ +using Application.Common.Mappings; +using Application.Common.Models.Dtos.Physical; +using Application.Lockers.Queries; +using AutoMapper; +using FluentAssertions; +using Xunit; + +namespace Application.Tests.Integration.Lockers.Queries; + +public class GetAllLockersPaginatedTests : BaseClassFixture +{ + private readonly IMapper _mapper; + public GetAllLockersPaginatedTests(CustomApiFactory apiFactory) : base(apiFactory) + { + var configuration = new MapperConfiguration(config => config.AddProfile()); + + _mapper = configuration.CreateMapper(); + } + + [Fact] + public async Task ShouldReturnLockers() + { + // Arrange + var department = CreateDepartment(); + var locker1 = CreateLocker(); + var locker2 = CreateLocker(); + var room = CreateRoom(department, locker1, locker2); + await AddAsync(room); + + var query = new GetAllLockersPaginated.Query(); + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(2); + result.Items.Should().BeEquivalentTo(_mapper.Map(new[] { locker1, locker2 }) + .OrderBy(x => x.Id), config => config.IgnoringCyclicReferences()); + + // Cleanup + Remove(locker1); + Remove(locker2); + Remove(room); + Remove(department); + } + + [Fact] + public async Task ShouldReturnLockersOfOneRoom_WhenSpecifyIdOfThatRoom() + { + // Arrange + var department1 = CreateDepartment(); + var department2 = CreateDepartment(); + var locker1 = CreateLocker(); + var locker2 = CreateLocker(); + var room1 = CreateRoom(department1, locker1, locker2); + var locker3 = CreateLocker(); + var locker4 = CreateLocker(); + var room2 = CreateRoom(department2, locker3, locker4); + await AddAsync(room1); + await AddAsync(room2); + + var query = new GetAllLockersPaginated.Query() + { + RoomId = room1.Id, + }; + + // Act + var result = await SendAsync(query); + + // Assert + result.TotalCount.Should().Be(2); + result.Items.Should().ContainEquivalentOf(_mapper.Map(locker1), + config => config.IgnoringCyclicReferences()); + result.Items.Should().ContainEquivalentOf(_mapper.Map(locker2), + config => config.IgnoringCyclicReferences()); + result.Items.Should().NotContainEquivalentOf(_mapper.Map(locker3), + config => config.IgnoringCyclicReferences()); + result.Items.Should().NotContainEquivalentOf(_mapper.Map(locker4), + config => config.IgnoringCyclicReferences()); + + // Cleanup + Remove(locker1); + Remove(locker2); + Remove(room1); + Remove(room2); + Remove(department1); + Remove(department2); + } +} \ No newline at end of file