diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index a12c85662..7c3dd4376 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using API.Data; +using API.Data.Repositories; using API.DTOs; using API.DTOs.Search; using API.Extensions; @@ -51,23 +52,21 @@ public class SearchController : BaseApiController /// Search for different entities against the query string /// /// + /// Defaults to 15, if 0 will not apply any limit to search results and may result in longer response times /// [HttpGet("search")] - public async Task> Search(string queryString) + public async Task> Search(string queryString, int maxRecords = 15) { queryString = Uri.UnescapeDataString(queryString) .Trim() .Replace(@"%", string.Empty) .Replace(":", string.Empty); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); - - // Get libraries user has access to - var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(user.Id)).ToList(); - if (!libraries.Any()) return BadRequest("User does not have access to any libraries"); + var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Library); + if (!user.Libraries.Any()) return BadRequest("User does not have access to any libraries"); var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user); var series = await _unitOfWork.SeriesRepository.SearchSeries(user.Id, isAdmin, - libraries.Select(l => l.Id).ToArray(), queryString); + user.Libraries.Select(l => l.Id).ToArray(), queryString, maxRecords); return Ok(series); } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 8810c5a24..0ab81b4c7 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -287,6 +287,15 @@ public class SeriesRepository : ISeriesRepository }; } + /// + /// + /// + /// + /// + /// + /// + /// If 0 or less, will not apply any LIMIT + /// public async Task SearchSeries(int userId, bool isAdmin, int[] libraryIds, string searchQuery, int maxRecords = 15) { var result = new SearchResultGroupDto(); @@ -304,7 +313,7 @@ public class SeriesRepository : ISeriesRepository .Where(l => EF.Functions.Like(l.Name, $"%{searchQuery}%")) .OrderBy(l => l.Name) .AsSplitQuery() - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -324,7 +333,7 @@ public class SeriesRepository : ISeriesRepository .OrderBy(s => s.SortName) .AsNoTracking() .AsSplitQuery() - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -334,7 +343,7 @@ public class SeriesRepository : ISeriesRepository .RestrictAgainstAgeRestriction(userRating) .AsSplitQuery() .OrderBy(c => c.NormalizedTitle) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -346,7 +355,7 @@ public class SeriesRepository : ISeriesRepository .AsNoTracking() .AsSplitQuery() .OrderBy(s => s.Title) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -356,7 +365,7 @@ public class SeriesRepository : ISeriesRepository .AsSplitQuery() .Distinct() .OrderBy(p => p.NormalizedName) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -366,7 +375,7 @@ public class SeriesRepository : ISeriesRepository .AsSplitQuery() .Distinct() .OrderBy(t => t.Title) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -376,7 +385,7 @@ public class SeriesRepository : ISeriesRepository .AsSplitQuery() .Distinct() .OrderBy(t => t.Title) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -391,7 +400,7 @@ public class SeriesRepository : ISeriesRepository .Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id)) .AsSplitQuery() .OrderBy(f => f.Id) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); @@ -402,7 +411,7 @@ public class SeriesRepository : ISeriesRepository .Where(c => c.Files.All(f => fileIds.Contains(f.Id))) .AsSplitQuery() .OrderBy(c => c.Id) - .Take(maxRecords) + .TakeIfGreaterThan0(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); diff --git a/API/Data/Repositories/UserRepository.cs b/API/Data/Repositories/UserRepository.cs index 904cc64b1..532359461 100644 --- a/API/Data/Repositories/UserRepository.cs +++ b/API/Data/Repositories/UserRepository.cs @@ -29,7 +29,7 @@ public enum AppUserIncludes WantToRead = 64, ReadingListsWithItems = 128, Devices = 256, - + Library = 512, } public interface IUserRepository @@ -202,9 +202,12 @@ public class UserRepository : IUserRepository query = query.Include(u => u.Devices); } + if (includeFlags.HasFlag(AppUserIncludes.Library)) + { + query = query.Include(u => u.Libraries); + } - - return query; + return query.AsSplitQuery(); } diff --git a/API/Extensions/QueryableExtensions.cs b/API/Extensions/QueryableExtensions.cs index ec0b81257..1113e6e48 100644 --- a/API/Extensions/QueryableExtensions.cs +++ b/API/Extensions/QueryableExtensions.cs @@ -110,4 +110,21 @@ public static class QueryableExtensions }) .SingleAsync(); } + + /// + /// Only applies the Take if it's greater than 0 + /// + /// + /// + /// + /// + public static IQueryable TakeIfGreaterThan0(this IQueryable queryable, int takeAmt) + { + if (takeAmt > 0) + { + return queryable.Take(takeAmt); + } + + return queryable; + } } diff --git a/UI/Web/src/app/_services/search.service.ts b/UI/Web/src/app/_services/search.service.ts index 78868fc24..ea195919a 100644 --- a/UI/Web/src/app/_services/search.service.ts +++ b/UI/Web/src/app/_services/search.service.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { distinctUntilChanged, filter, map, Observable, of, ReplaySubject, startWith, switchMap } from 'rxjs'; +import { distinctUntilChanged, map, Observable, of, ReplaySubject, startWith, switchMap } from 'rxjs'; import { environment } from 'src/environments/environment'; import { SearchResultGroup } from '../_models/search/search-result-group'; import { Series } from '../_models/series';