diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 722a3b310..a12c85662 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -47,20 +47,27 @@ public class SearchController : BaseApiController return Ok(await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapterId, userId)); } + /// + /// Search for different entities against the query string + /// + /// + /// [HttpGet("search")] public async Task> Search(string queryString) { - queryString = Uri.UnescapeDataString(queryString).Trim().Replace(@"%", string.Empty).Replace(":", string.Empty); - + 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"); - if (!libraries.Any()) return BadRequest("User does not have access to any libraries"); - if (!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); + var series = await _unitOfWork.SeriesRepository.SearchSeries(user.Id, isAdmin, + libraries.Select(l => l.Id).ToArray(), queryString); return Ok(series); } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 4423db98d..8810c5a24 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -62,7 +62,7 @@ public interface ISeriesRepository /// /// /// - Task SearchSeries(int userId, bool isAdmin, int[] libraryIds, string searchQuery); + Task SearchSeries(int userId, bool isAdmin, int[] libraryIds, string searchQuery, int maxRecords = 15); Task> GetSeriesForLibraryIdAsync(int libraryId, SeriesIncludes includes = SeriesIncludes.None); Task GetSeriesDtoByIdAsync(int seriesId, int userId); Task GetSeriesByIdAsync(int seriesId, SeriesIncludes includes = SeriesIncludes.Volumes | SeriesIncludes.Metadata); @@ -287,27 +287,26 @@ public class SeriesRepository : ISeriesRepository }; } - public async Task SearchSeries(int userId, bool isAdmin, int[] libraryIds, string searchQuery) + public async Task SearchSeries(int userId, bool isAdmin, int[] libraryIds, string searchQuery, int maxRecords = 15) { - const int maxRecords = 15; var result = new SearchResultGroupDto(); var searchQueryNormalized = Services.Tasks.Scanner.Parser.Parser.Normalize(searchQuery); var userRating = await _context.AppUser.GetUserAgeRestriction(userId); - var seriesIds = _context.Series + var seriesIds = await _context.Series .Where(s => libraryIds.Contains(s.LibraryId)) .RestrictAgainstAgeRestriction(userRating) .Select(s => s.Id) - .ToList(); + .ToListAsync(); - result.Libraries = await _context.Library + result.Libraries = _context.Library .Where(l => libraryIds.Contains(l.Id)) .Where(l => EF.Functions.Like(l.Name, $"%{searchQuery}%")) .OrderBy(l => l.Name) .AsSplitQuery() .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); var justYear = Regex.Match(searchQuery, @"\d{4}").Value; var hasYearInQuery = !string.IsNullOrEmpty(justYear); @@ -329,56 +328,57 @@ public class SeriesRepository : ISeriesRepository .ProjectTo(_mapper.ConfigurationProvider) .AsEnumerable(); - result.ReadingLists = await _context.ReadingList + result.ReadingLists = _context.ReadingList .Where(rl => rl.AppUserId == userId || rl.Promoted) .Where(rl => EF.Functions.Like(rl.Title, $"%{searchQuery}%")) .RestrictAgainstAgeRestriction(userRating) .AsSplitQuery() + .OrderBy(c => c.NormalizedTitle) .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); - result.Collections = await _context.CollectionTag + result.Collections = _context.CollectionTag .Where(c => EF.Functions.Like(c.Title, $"%{searchQuery}%") || EF.Functions.Like(c.NormalizedTitle, $"%{searchQueryNormalized}%")) .Where(c => c.Promoted || isAdmin) .RestrictAgainstAgeRestriction(userRating) - .OrderBy(s => s.Title) .AsNoTracking() .AsSplitQuery() + .OrderBy(s => s.Title) .Take(maxRecords) - .OrderBy(c => c.NormalizedTitle) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); - result.Persons = await _context.SeriesMetadata + result.Persons = _context.SeriesMetadata .Where(sm => seriesIds.Contains(sm.SeriesId)) .SelectMany(sm => sm.People.Where(t => EF.Functions.Like(t.Name, $"%{searchQuery}%"))) .AsSplitQuery() - .Take(maxRecords) .Distinct() + .OrderBy(p => p.NormalizedName) + .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); - result.Genres = await _context.SeriesMetadata + result.Genres = _context.SeriesMetadata .Where(sm => seriesIds.Contains(sm.SeriesId)) .SelectMany(sm => sm.Genres.Where(t => EF.Functions.Like(t.Title, $"%{searchQuery}%"))) .AsSplitQuery() - .OrderBy(t => t.Title) .Distinct() + .OrderBy(t => t.Title) .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); - result.Tags = await _context.SeriesMetadata + result.Tags = _context.SeriesMetadata .Where(sm => seriesIds.Contains(sm.SeriesId)) .SelectMany(sm => sm.Tags.Where(t => EF.Functions.Like(t.Title, $"%{searchQuery}%"))) .AsSplitQuery() - .OrderBy(t => t.Title) .Distinct() + .OrderBy(t => t.Title) .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); var fileIds = _context.Series .Where(s => seriesIds.Contains(s.Id)) @@ -387,22 +387,24 @@ public class SeriesRepository : ISeriesRepository .SelectMany(v => v.Chapters) .SelectMany(c => c.Files.Select(f => f.Id)); - result.Files = await _context.MangaFile + result.Files = _context.MangaFile .Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id)) .AsSplitQuery() + .OrderBy(f => f.Id) .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); - result.Chapters = await _context.Chapter + result.Chapters = _context.Chapter .Include(c => c.Files) .Where(c => EF.Functions.Like(c.TitleName, $"%{searchQuery}%")) .Where(c => c.Files.All(f => fileIds.Contains(f.Id))) .AsSplitQuery() + .OrderBy(c => c.Id) .Take(maxRecords) .ProjectTo(_mapper.ConfigurationProvider) - .ToListAsync(); + .AsEnumerable(); return result; }