diff --git a/back/.editorconfig b/back/.editorconfig index 985604f6..5f7f01a5 100644 --- a/back/.editorconfig +++ b/back/.editorconfig @@ -16,6 +16,7 @@ dotnet_diagnostic.IDE0058.severity = none dotnet_diagnostic.IDE0046.severity = none dotnet_diagnostic.CA1848.severity = none dotnet_diagnostic.CA2007.severity = none +dotnet_diagnostic.CA1716.severity = none # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true csharp_using_directive_placement = outside_namespace:warning diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs index 3b1a1b7f..624f72eb 100644 --- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs +++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Abstractions.Models; @@ -256,7 +255,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of the source object /// The related resource's type /// The param - /// + /// /// /// Task Load([NotNull] T obj, Expression> member, bool force = false) @@ -274,7 +273,7 @@ namespace Kyoo.Abstractions.Controllers /// The type of the source object /// The related resource's type /// The param - /// + /// /// /// Task Load([NotNull] T obj, Expression>> member, bool force = false) @@ -291,8 +290,8 @@ namespace Kyoo.Abstractions.Controllers /// /// The type of the source object /// The param - /// - /// + /// + /// /// Task Load([NotNull] T obj, string memberName, bool force = false) where T : class, IResource; @@ -305,8 +304,8 @@ namespace Kyoo.Abstractions.Controllers /// /// true if you want to load the relation even if it is not null, false otherwise. /// - /// - /// + /// + /// /// /// A representing the asynchronous operation. Task Load([NotNull] IResource obj, string memberName, bool force = false); @@ -325,21 +324,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The ID of the library - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No library exist with the given ID. - /// A list of items that match every filters - Task> GetItemsFromLibrary(int id, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetItemsFromLibrary(id, where, new Sort(sort), limit); - /// /// Get items (A wrapper around shows or collections) from a library. /// @@ -354,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The slug of the library - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No library exist with the given slug. - /// A list of items that match every filters - Task> GetItemsFromLibrary(string slug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetItemsFromLibrary(slug, where, new Sort(sort), limit); - /// /// Get people's roles from a show. /// @@ -383,21 +352,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a show. - /// - /// The ID of the show - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given ID. - /// A list of items that match every filters - Task> GetPeopleFromShow(int showID, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetPeopleFromShow(showID, where, new Sort(sort), limit); - /// /// Get people's roles from a show. /// @@ -412,21 +366,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a show. - /// - /// The slug of the show - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given slug. - /// A list of items that match every filters - Task> GetPeopleFromShow(string showSlug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); - /// /// Get people's roles from a person. /// @@ -441,21 +380,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a person. - /// - /// The id of the person - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given ID. - /// A list of items that match every filters - Task> GetRolesFromPeople(int id, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetRolesFromPeople(id, where, new Sort(sort), limit); - /// /// Get people's roles from a person. /// @@ -470,21 +394,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a person. - /// - /// The slug of the person - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given slug. - /// A list of items that match every filters - Task> GetRolesFromPeople(string slug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetRolesFromPeople(slug, where, new Sort(sort), limit); - /// /// Setup relations between a show, a library and a collection /// @@ -516,22 +425,6 @@ namespace Kyoo.Abstractions.Controllers Pagination limit = default) where T : class, IResource; - /// - /// Get all resources with filters - /// - /// A filter function - /// A sort by function - /// How many items to return and where to start - /// The type of resources to load - /// A list of resources that match every filters - Task> GetAll([Optional] Expression> where, - Expression> sort, - Pagination limit = default) - where T : class, IResource - { - return GetAll(where, new Sort(sort), limit); - } - /// /// Get the count of resources that match the filter /// diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs index 1d996e97..229fc4ce 100644 --- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs +++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Abstractions.Models; @@ -106,19 +105,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get every resources that match all filters - /// - /// A filter predicate - /// A sort by predicate. The order is ascending. - /// How pagination should be done (where to start and how many to return) - /// A list of resources that match every filters - [ItemNotNull] - Task> GetAll([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetAll(where, new Sort(sort), limit); - /// /// Get the number of resources that match the filter's predicate. /// @@ -352,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The ID of the library - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No library exist with the given ID. - /// A list of items that match every filters - public Task> GetFromLibrary(int id, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromLibrary(id, where, new Sort(sort), limit); - /// /// Get items (A wrapper around shows or collections) from a library. /// @@ -380,21 +351,6 @@ namespace Kyoo.Abstractions.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - - /// - /// Get items (A wrapper around shows or collections) from a library. - /// - /// The slug of the library - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No library exist with the given slug. - /// A list of items that match every filters - public Task> GetFromLibrary(string slug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromLibrary(slug, where, new Sort(sort), limit); } /// @@ -431,21 +387,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a show. - /// - /// The ID of the show - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given ID. - /// A list of items that match every filters - Task> GetFromShow(int showID, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromShow(showID, where, new Sort(sort), limit); - /// /// Get people's roles from a show. /// @@ -460,21 +401,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a show. - /// - /// The slug of the show - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given slug. - /// A list of items that match every filters - Task> GetFromShow(string showSlug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromShow(showSlug, where, new Sort(sort), limit); - /// /// Get people's roles from a person. /// @@ -489,21 +415,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default); - /// - /// Get people's roles from a person. - /// - /// The id of the person - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given ID. - /// A list of items that match every filters - Task> GetFromPeople(int id, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromPeople(id, where, new Sort(sort), limit); - /// /// Get people's roles from a person. /// @@ -517,21 +428,6 @@ namespace Kyoo.Abstractions.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - - /// - /// Get people's roles from a person. - /// - /// The slug of the person - /// A filter function - /// A sort by method - /// How many items to return and where to start - /// No exist with the given slug. - /// A list of items that match every filters - Task> GetFromPeople(string slug, - [Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) => GetFromPeople(slug, where, new Sort(sort), limit); } /// @@ -551,21 +447,6 @@ namespace Kyoo.Abstractions.Controllers Sort sort = default, Pagination limit = default) where T : class, IMetadata; - - /// - /// Get a list of external ids that match all filters - /// - /// A predicate to add arbitrary filter - /// A sort by expression - /// Pagination information (where to start and how many to get) - /// The type of metadata to retrieve - /// A filtered list of external ids. - Task> GetMetadataID([Optional] Expression> where, - Expression> sort, - Pagination limit = default - ) - where T : class, IMetadata - => GetMetadataID(where, new Sort(sort), limit); } /// diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 7ded5348..0c56b2b1 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -113,7 +113,7 @@ namespace Kyoo.Abstractions.Models.Utils /// /// A matcher overload for nullable IDs. See - /// + /// /// for more details. /// /// An expression to retrieve an ID from the type . diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Pagination.cs b/back/src/Kyoo.Abstractions/Models/Utils/Pagination.cs index 233b317d..935393b3 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Pagination.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Pagination.cs @@ -33,15 +33,22 @@ namespace Kyoo.Abstractions.Controllers /// public int? AfterID { get; } + /// + /// Should the previous page be returned instead of the next? + /// + public bool Reverse { get; } + /// /// Create a new instance. /// /// Set the value /// Set the value. If not specified, it will start from the start - public Pagination(int count, int? afterID = null) + /// Should the previous page be returned instead of the next? + public Pagination(int count, int? afterID = null, bool reverse = false) { Count = count; AfterID = afterID; + Reverse = reverse; } /// diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs index 1aa88673..4cafb788 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs @@ -17,7 +17,9 @@ // along with Kyoo. If not, see . using System; +using System.Linq; using System.Linq.Expressions; +using System.Reflection; using Kyoo.Utils; namespace Kyoo.Abstractions.Controllers @@ -26,63 +28,73 @@ namespace Kyoo.Abstractions.Controllers /// Information about how a query should be sorted. What factor should decide the sort and in which order. /// /// For witch type this sort applies - public readonly struct Sort + public record Sort { /// - /// The sort key. This member will be used to sort the results. + /// Sort by a specific key /// - public Expression> Key { get; } - - /// + /// The sort keys. This members will be used to sort the results. + /// /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order. - /// - public bool Descendant { get; } + /// + public record By(string key, bool desendant = false) : Sort + { + /// + /// Sort by a specific key + /// + /// The sort keys. This members will be used to sort the results. + /// + /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order. + /// + public By(Expression> key, bool desendant = false) + : this(Utility.GetPropertyName(key), desendant) { } + + /// + /// Create a new instance from a key's name (case insensitive). + /// + /// A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key". + /// An invalid key or sort specifier as been given. + /// A for the given string + public static new By From(string sortBy) + { + string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; + string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; + bool desendant = order switch + { + "desc" => true, + "asc" => false, + null => false, + _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") + }; + PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); + if (property == null) + throw new ArgumentException("The given sort key is not valid."); + return new By(property.Name, desendant); + } + } /// - /// Create a new instance. + /// Sort by multiple keys. /// - /// The sort key given. It is assigned to . - /// Should this be in descendant order? The default is false. - /// If the given key is not a member. - public Sort(Expression> key, bool descendant = false) - { - Key = key; - Descendant = descendant; + /// The list of keys to sort by. + public record Conglomerate(params By[] list) : Sort; - if (!Utility.IsPropertyExpression(Key)) - throw new ArgumentException("The given sort key is not valid."); - } + /// The default sort method for the given type. + public record Default : Sort; /// /// Create a new instance from a key's name (case insensitive). /// /// A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key". /// An invalid key or sort specifier as been given. - public Sort(string sortBy) + /// A for the given string + public static Sort From(string sortBy) { if (string.IsNullOrEmpty(sortBy)) - { - Key = null; - Descendant = false; - return; - } - - string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; - string order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; - - ParameterExpression param = Expression.Parameter(typeof(T), "x"); - MemberExpression property = Expression.Property(param, key); - Key = property.Type.IsValueType - ? Expression.Lambda>(Expression.Convert(property, typeof(object)), param) - : Expression.Lambda>(property, param); - - Descendant = order switch - { - "desc" => true, - "asc" => false, - null => false, - _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") - }; + return new Default(); + if (sortBy.Contains(',')) + return new Conglomerate(sortBy.Split(',').Select(By.From).ToArray()); + return By.From(sortBy); } } } diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index bded4aec..10bdfae4 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -163,48 +163,46 @@ namespace Kyoo.Abstractions.Models await library.Load(ep, x => x.Show); await library.Load(ep, x => x.Tracks); - Episode previous = null; - Episode next = null; - if (!ep.Show.IsMovie) - { - if (ep.AbsoluteNumber != null) - { - previous = await library.GetOrDefault( - x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber, - new Sort(x => x.AbsoluteNumber, true) - ); - next = await library.GetOrDefault( - x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber, - new Sort(x => x.AbsoluteNumber) - ); - } - else if (ep.SeasonNumber != null && ep.EpisodeNumber != null) - { - previous = await library.GetOrDefault( - x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber - && x.EpisodeNumber < ep.EpisodeNumber, - new Sort(x => x.EpisodeNumber, true) - ); - previous ??= await library.GetOrDefault( - x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber - 1, - new Sort(x => x.EpisodeNumber, true) - ); - - next = await library.GetOrDefault( - x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber - && x.EpisodeNumber > ep.EpisodeNumber, - new Sort(x => x.EpisodeNumber) - ); - next ??= await library.GetOrDefault( - x => x.ShowID == ep.ShowID - && x.SeasonNumber == ep.SeasonNumber + 1, - new Sort(x => x.EpisodeNumber) - ); - } - } + // if (!ep.Show.IsMovie) + // { + // if (ep.AbsoluteNumber != null) + // { + // previous = await library.GetOrDefault( + // x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber, + // new Sort(x => x.AbsoluteNumber, true) + // ); + // next = await library.GetOrDefault( + // x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber, + // new Sort(x => x.AbsoluteNumber) + // ); + // } + // else if (ep.SeasonNumber != null && ep.EpisodeNumber != null) + // { + // previous = await library.GetOrDefault( + // x => x.ShowID == ep.ShowID + // && x.SeasonNumber == ep.SeasonNumber + // && x.EpisodeNumber < ep.EpisodeNumber, + // new Sort(x => x.EpisodeNumber, true) + // ); + // previous ??= await library.GetOrDefault( + // x => x.ShowID == ep.ShowID + // && x.SeasonNumber == ep.SeasonNumber - 1, + // new Sort(x => x.EpisodeNumber, true) + // ); + // + // next = await library.GetOrDefault( + // x => x.ShowID == ep.ShowID + // && x.SeasonNumber == ep.SeasonNumber + // && x.EpisodeNumber > ep.EpisodeNumber, + // new Sort(x => x.EpisodeNumber) + // ); + // next ??= await library.GetOrDefault( + // x => x.ShowID == ep.ShowID + // && x.SeasonNumber == ep.SeasonNumber + 1, + // new Sort(x => x.EpisodeNumber) + // ); + // } + // } return new WatchItem { @@ -225,8 +223,12 @@ namespace Kyoo.Abstractions.Models Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), Fonts = await transcoder.ListFonts(ep), - PreviousEpisode = previous, - NextEpisode = next, + PreviousEpisode = ep.Show.IsMovie + ? null + : (await library.GetAll(limit: new Pagination(1, ep.ID, true))).FirstOrDefault(), + NextEpisode = ep.Show.IsMovie + ? null + : (await library.GetAll(limit: new Pagination(1, ep.ID))).FirstOrDefault(), Chapters = await _GetChapters(ep, fs), IsMovie = ep.Show.IsMovie }; diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index f105c6da..ab54d442 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -44,7 +43,7 @@ namespace Kyoo.Core.Controllers private readonly IProviderRepository _providers; /// - protected override Expression> DefaultSort => x => x.Name; + protected override Sort DefaultSort => new Sort.By(nameof(Collection.Name)); /// /// Create a new . @@ -61,11 +60,11 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Collections - .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) - .OrderBy(DefaultSort) - .Take(20) - .ToListAsync(); + return await Sort( + _database.Collections + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) + .Take(20) + ).ToListAsync(); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index 4a139a33..6040b1d2 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -51,7 +50,12 @@ namespace Kyoo.Core.Controllers private readonly ITrackRepository _tracks; /// - protected override Expression> DefaultSort => x => x.EpisodeNumber; + // Use absolute numbers by default and fallback to season/episodes if it does not exists. + protected override Sort DefaultSort => new Sort.Conglomerate( + new Sort.By(x => x.AbsoluteNumber), + new Sort.By(x => x.SeasonNumber), + new Sort.By(x => x.EpisodeNumber) + ); /// /// Create a new . @@ -120,11 +124,12 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - List ret = await _database.Episodes - .Include(x => x.Show) - .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) - .Where(_database.Like(x => x.Title, $"%{query}%")) - .OrderBy(DefaultSort) + List ret = await Sort( + _database.Episodes + .Include(x => x.Show) + .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) + .Where(_database.Like(x => x.Title, $"%{query}%")) + ) .Take(20) .ToListAsync(); foreach (Episode ep in ret) diff --git a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs index eb12a4c4..de104312 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs @@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers private readonly DatabaseContext _database; /// - protected override Expression> DefaultSort => x => x.Slug; + protected override Sort DefaultSort => new Sort.By(x => x.Slug); /// /// Create a new . @@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Genres - .Where(_database.Like(x => x.Name, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Genres + .Where(_database.Like(x => x.Name, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs index 833a06cc..4ec4781e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryItemRepository.cs @@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers private readonly Lazy _libraries; /// - protected override Expression> DefaultSort => x => x.Title; + protected override Sort DefaultSort => new Sort.By(x => x.Title); /// /// Create a new . @@ -92,9 +92,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.LibraryItems - .Where(_database.Like(x => x.Title, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.LibraryItems + .Where(_database.Like(x => x.Title, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs index 494273ff..76bd2858 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs @@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers private readonly IProviderRepository _providers; /// - protected override Expression> DefaultSort => x => x.ID; + protected override Sort DefaultSort => new Sort.By(x => x.ID); /// /// Create a new instance. @@ -62,9 +62,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Libraries - .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Libraries + .Where(_database.Like(x => x.Name + " " + x.Slug, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 59928d02..0cb0600e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -47,7 +48,7 @@ namespace Kyoo.Core.Controllers /// /// The default sort order that will be used for this resource's type. /// - protected abstract Expression> DefaultSort { get; } + protected abstract Sort DefaultSort { get; } /// /// Create a new base with the given database handle. @@ -61,6 +62,206 @@ namespace Kyoo.Core.Controllers /// public Type RepositoryType => typeof(T); + /// + /// Sort the given query. + /// + /// The query to sort. + /// How to sort the query + /// The newly sorted query. + protected IOrderedQueryable Sort(IQueryable query, Sort sortBy = null) + { + sortBy ??= DefaultSort; + + switch (sortBy) + { + case Sort.Default: + return Sort(query, DefaultSort); + case Sort.By(var key, var desc): + return desc + ? query.OrderByDescending(x => EF.Property(x, key)) + : query.OrderBy(x => EF.Property(x, key)); + case Sort.Conglomerate(var keys): + IOrderedQueryable nQuery = Sort(query, keys[0]); + foreach ((string key, bool desc) in keys.Skip(1)) + { + nQuery = desc + ? nQuery.ThenByDescending(x => EF.Property(x, key)) + : nQuery.ThenBy(x => EF.Property(x, key)); + } + return nQuery; + default: + // The language should not require me to do this... + throw new SwitchExpressionException(); + } + } + + private static Func GetComparisonExpression( + bool desc, + bool next, + bool orEqual) + { + bool greaterThan = desc ^ next; + + return orEqual + ? (greaterThan ? Expression.GreaterThanOrEqual : Expression.LessThanOrEqual) + : (greaterThan ? Expression.GreaterThan : Expression.LessThan); + } + + + /// + /// Create a filter (where) expression on the query to skip everything before/after the referenceID. + /// The generalized expression for this in pseudocode is: + /// (x > a) OR + /// (x = a AND y > b) OR + /// (x = a AND y = b AND z > c) OR... + /// + /// Of course, this will be a bit more complex when ASC and DESC are mixed. + /// Assume x is ASC, y is DESC, and z is ASC: + /// (x > a) OR + /// (x = a AND y < b) OR + /// (x = a AND y = b AND z > c) OR... + /// + protected Expression> KeysetPaginatate( + Sort sort, + T reference, + bool next = true) + { + if (sort is Sort.Default) + sort = DefaultSort; + + // x => + ParameterExpression x = Expression.Parameter(typeof(T), "x"); + ConstantExpression referenceC = Expression.Constant(reference, typeof(T)); + + if (sort is Sort.By(var key, var desc)) + { + Func comparer = GetComparisonExpression(desc, next, false); + MemberExpression xkey = Expression.Property(x, key); + MemberExpression rkey = Expression.Property(referenceC, key); + BinaryExpression compare = ApiHelper.StringCompatibleExpression(comparer, xkey, rkey); + return Expression.Lambda>(compare, x); + } + + if (sort is Sort.Conglomerate(var list)) + { + throw new NotImplementedException(); + // BinaryExpression orExpression; + // + // foreach ((string key, bool desc) in list) + // { + // query.Where(x => + // } + } + throw new SwitchExpressionException(); + + // Shamlessly stollen from https://github.com/mrahhal/MR.EntityFrameworkCore.KeysetPagination/blob/main/src/MR.EntityFrameworkCore.KeysetPagination/KeysetPaginationExtensions.cs#L191 + // // A composite keyset pagination in sql looks something like this: + // // (x, y, ...) > (a, b, ...) + // // Where, x/y/... represent the column and a/b/... represent the reference's respective values. + // // + // // In sql standard this syntax is called "row value". Check here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-row-values + // // Unfortunately, not all databases support this properly. + // // Further, if we were to use this we would somehow need EF Core to recognise it and translate it + // // perhaps by using a new DbFunction (https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctions). + // // There's an ongoing issue for this here: https://github.com/dotnet/efcore/issues/26822 + // // + // // In addition, row value won't work for mixed ordered columns. i.e if x > a but y < b. + // // So even if we can use it we'll still have to fallback to this logic in these cases. + // // + // // The generalized expression for this in pseudocode is: + // // (x > a) OR + // // (x = a AND y > b) OR + // // (x = a AND y = b AND z > c) OR... + // // + // // Of course, this will be a bit more complex when ASC and DESC are mixed. + // // Assume x is ASC, y is DESC, and z is ASC: + // // (x > a) OR + // // (x = a AND y < b) OR + // // (x = a AND y = b AND z > c) OR... + // // + // // An optimization is to include an additional redundant wrapping clause for the 1st column when there are + // // more than one column we're acting on, which would allow the db to use it as an access predicate on the 1st column. + // // See here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic + // + // var referenceValues = GetValues(columns, reference); + // + // MemberExpression firstMemberAccessExpression; + // Expression firstReferenceValueExpression; + // + // // entity => + // ParameterExpression param = Expression.Parameter(typeof(T), "entity"); + // + // BinaryExpression orExpression; + // int innerLimit = 1; + // // This loop compounds the outer OR expressions. + // for (int i = 0; i < sort.list.Length; i++) + // { + // BinaryExpression andExpression; + // + // // This loop compounds the inner AND expressions. + // // innerLimit implicitly grows from 1 to items.Count by each iteration. + // for (int j = 0; j < innerLimit; j++) + // { + // bool isInnerLastOperation = j + 1 == innerLimit; + // var column = columns[j]; + // var memberAccess = column.MakeMemberAccessExpression(param); + // var referenceValue = referenceValues[j]; + // Expression> referenceValueFunc = () => referenceValue; + // var referenceValueExpression = referenceValueFunc.Body; + // + // if (firstMemberAccessExpression == null) + // { + // // This might be used later on in an optimization. + // firstMemberAccessExpression = memberAccess; + // firstReferenceValueExpression = referenceValueExpression; + // } + // + // BinaryExpression innerExpression; + // if (!isInnerLastOperation) + // { + // innerExpression = Expression.Equal( + // memberAccess, + // EnsureMatchingType(memberAccess, referenceValueExpression)); + // } + // else + // { + // var compare = GetComparisonExpressionToApply(direction, column, orEqual: false); + // innerExpression = MakeComparisonExpression( + // column, + // memberAccess, referenceValueExpression, + // compare); + // } + // + // andExpression = andExpression == null ? innerExpression : Expression.And(andExpression, innerExpression); + // } + // + // orExpression = orExpression == null ? andExpression : Expression.Or(orExpression, andExpression); + // + // innerLimit++; + // } + // + // var finalExpression = orExpression; + // if (columns.Count > 1) + // { + // // Implement the optimization that allows an access predicate on the 1st column. + // // This is done by generating the following expression: + // // (x >=|<= a) AND (previous generated expression) + // // + // // This effectively adds a redundant clause on the 1st column, but it's a clause all dbs + // // understand and can use as an access predicate (most commonly when the column is indexed). + // + // var firstColumn = columns[0]; + // var compare = GetComparisonExpressionToApply(direction, firstColumn, orEqual: true); + // var accessPredicateClause = MakeComparisonExpression( + // firstColumn, + // firstMemberAccessExpression!, firstReferenceValueExpression!, + // compare); + // finalExpression = Expression.And(accessPredicateClause, finalExpression); + // } + // + // return Expression.Lambda>(finalExpression, param); + } + /// /// Get a resource from it's ID and make the instance track it. /// @@ -117,10 +318,7 @@ namespace Kyoo.Core.Controllers /// public virtual Task GetOrDefault(Expression> where, Sort sortBy = default) { - IQueryable query = Database.Set(); - Expression> sortKey = sortBy.Key ?? DefaultSort; - query = sortBy.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); - return query.FirstOrDefaultAsync(where); + return Sort(Database.Set(), sortBy).FirstOrDefaultAsync(where); } /// @@ -142,54 +340,19 @@ namespace Kyoo.Core.Controllers /// The sort settings (sort order and sort by) /// Pagination information (where to start and how many to get) /// The filtered query - protected Task> ApplyFilters(IQueryable query, + protected async Task> ApplyFilters(IQueryable query, Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit); - } - - /// - /// Apply filters to a query to ease sort, pagination and where queries for any resources types. - /// For resources of type , see - /// - /// The base query to filter. - /// A function to asynchronously get a resource from the database using it's ID. - /// The default sort order of this resource's type. - /// An expression to filter based on arbitrary conditions - /// The sort settings (sort order and sort by) - /// Pagination information (where to start and how many to get) - /// The type of items to query. - /// The filtered query - protected async Task> ApplyFilters(IQueryable query, - Func> get, - Expression> defaultSort, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { + query = Sort(query, sort); if (where != null) query = query.Where(where); - Expression> sortKey = sort.Key ?? defaultSort; - Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert - ? ((UnaryExpression)sortKey.Body).Operand - : sortKey.Body; - - if (typeof(Enum).IsAssignableFrom(sortExpression.Type)) - throw new ArgumentException("Invalid sort key."); - - query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); - if (limit.AfterID != null) { - TValue after = await get(limit.AfterID.Value); - Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type); - query = query.Where(Expression.Lambda>( - ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key), - sortKey.Parameters.First() - )); + T reference = await Get(limit.AfterID.Value); + query = query.Where(KeysetPaginatate(sort, reference)); } if (limit.Count > 0) query = query.Take(limit.Count); diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index 09809426..618587ff 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -51,7 +51,7 @@ namespace Kyoo.Core.Controllers private readonly Lazy _shows; /// - protected override Expression> DefaultSort => x => x.Name; + protected override Sort DefaultSort => new Sort.By(x => x.Name); /// /// Create a new @@ -72,9 +72,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.People - .Where(_database.Like(x => x.Name, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.People + .Where(_database.Like(x => x.Name, $"%{query}%")) + ) .Take(20) .ToListAsync(); } @@ -152,19 +153,19 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection people = await ApplyFilters(_database.PeopleRoles - .Where(x => x.ShowID == showID) - .Include(x => x.People), - id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), - x => x.People.Name, - where, - sort, - limit); - if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null) - throw new ItemNotFoundException(); - foreach (PeopleRole role in people) - role.ForPeople = true; - return people; + throw new NotImplementedException(); + // ICollection people = await ApplyFilters(_database.PeopleRoles + // .Where(x => x.ShowID == showID) + // .Include(x => x.People), + // x => x.People.Name, + // where, + // sort, + // limit); + // if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null) + // throw new ItemNotFoundException(); + // foreach (PeopleRole role in people) + // role.ForPeople = true; + // return people; } /// @@ -173,20 +174,21 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection people = await ApplyFilters(_database.PeopleRoles - .Where(x => x.Show.Slug == showSlug) - .Include(x => x.People) - .Include(x => x.Show), - id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), - x => x.People.Name, - where, - sort, - limit); - if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null) - throw new ItemNotFoundException(); - foreach (PeopleRole role in people) - role.ForPeople = true; - return people; + throw new NotImplementedException(); + // ICollection people = await ApplyFilters(_database.PeopleRoles + // .Where(x => x.Show.Slug == showSlug) + // .Include(x => x.People) + // .Include(x => x.Show), + // id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), + // x => x.People.Name, + // where, + // sort, + // limit); + // if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null) + // throw new ItemNotFoundException(); + // foreach (PeopleRole role in people) + // role.ForPeople = true; + // return people; } /// @@ -195,17 +197,18 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles - .Where(x => x.PeopleID == id) - .Include(x => x.Show), - y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y), - x => x.Show.Title, - where, - sort, - limit); - if (!roles.Any() && await GetOrDefault(id) == null) - throw new ItemNotFoundException(); - return roles; + throw new NotImplementedException(); + // ICollection roles = await ApplyFilters(_database.PeopleRoles + // .Where(x => x.PeopleID == id) + // .Include(x => x.Show), + // y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y), + // x => x.Show.Title, + // where, + // sort, + // limit); + // if (!roles.Any() && await GetOrDefault(id) == null) + // throw new ItemNotFoundException(); + // return roles; } /// @@ -214,17 +217,18 @@ namespace Kyoo.Core.Controllers Sort sort = default, Pagination limit = default) { - ICollection roles = await ApplyFilters(_database.PeopleRoles - .Where(x => x.People.Slug == slug) - .Include(x => x.Show), - id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), - x => x.Show.Title, - where, - sort, - limit); - if (!roles.Any() && await GetOrDefault(slug) == null) - throw new ItemNotFoundException(); - return roles; + throw new NotImplementedException(); + // ICollection roles = await ApplyFilters(_database.PeopleRoles + // .Where(x => x.People.Slug == slug) + // .Include(x => x.Show), + // id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), + // x => x.Show.Title, + // where, + // sort, + // limit); + // if (!roles.Any() && await GetOrDefault(slug) == null) + // throw new ItemNotFoundException(); + // return roles; } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs index f8787658..2c9cb799 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs @@ -49,14 +49,15 @@ namespace Kyoo.Core.Controllers } /// - protected override Expression> DefaultSort => x => x.Slug; + protected override Sort DefaultSort => new Sort.By(x => x.Slug); /// public override async Task> Search(string query) { - return await _database.Providers - .Where(_database.Like(x => x.Name, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Providers + .Where(_database.Like(x => x.Name, $"%{query}%")) + ) .Take(20) .ToListAsync(); } @@ -87,13 +88,14 @@ namespace Kyoo.Core.Controllers Pagination limit = default) where T : class, IMetadata { - return ApplyFilters(_database.MetadataIds() - .Include(y => y.Provider), - x => _database.MetadataIds().FirstOrDefaultAsync(y => y.ResourceID == x), - x => x.ResourceID, - where, - sort, - limit); + throw new NotImplementedException(); + // return ApplyFilters(_database.MetadataIds() + // .Include(y => y.Provider), + // x => _database.MetadataIds().FirstOrDefaultAsync(y => y.ResourceID == x), + // x => x.ResourceID, + // where, + // sort, + // limit); } } } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index db36be70..02f3b9a8 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers private readonly IProviderRepository _providers; /// - protected override Expression> DefaultSort => x => x.SeasonNumber; + protected override Sort DefaultSort => new Sort.By(x => x.SeasonNumber); /// /// Create a new . @@ -95,9 +95,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Seasons - .Where(_database.Like(x => x.Title, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Seasons + .Where(_database.Like(x => x.Title, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index fd86446e..efc2b00e 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -60,7 +60,7 @@ namespace Kyoo.Core.Controllers private readonly IProviderRepository _providers; /// - protected override Expression> DefaultSort => x => x.Title; + protected override Sort DefaultSort => new Sort.By(x => x.Title); /// /// Create a new . @@ -88,9 +88,10 @@ namespace Kyoo.Core.Controllers public override async Task> Search(string query) { query = $"%{query}%"; - return await _database.Shows - .Where(_database.Like(x => x.Title + " " + x.Slug, query)) - .OrderBy(DefaultSort) + return await Sort( + _database.Shows + .Where(_database.Like(x => x.Title + " " + x.Slug, query)) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index 77da65d5..fc9aeda6 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -44,7 +44,7 @@ namespace Kyoo.Core.Controllers private readonly IProviderRepository _providers; /// - protected override Expression> DefaultSort => x => x.Name; + protected override Sort DefaultSort => new Sort.By(x => x.Name); /// /// Create a new . @@ -61,9 +61,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Studios - .Where(_database.Like(x => x.Name, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Studios + .Where(_database.Like(x => x.Name, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs index 9930b17f..2528fc2b 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/TrackRepository.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; @@ -38,7 +37,7 @@ namespace Kyoo.Core.Controllers private readonly DatabaseContext _database; /// - protected override Expression> DefaultSort => x => x.TrackIndex; + protected override Sort DefaultSort => new Sort.By(x => x.TrackIndex); /// /// Create a new . diff --git a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs index d9fba4df..2f21f7c4 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers private readonly DatabaseContext _database; /// - protected override Expression> DefaultSort => x => x.Username; + protected override Sort DefaultSort => new Sort.By(x => x.Username); /// /// Create a new @@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers /// public override async Task> Search(string query) { - return await _database.Users - .Where(_database.Like(x => x.Username, $"%{query}%")) - .OrderBy(DefaultSort) + return await Sort( + _database.Users + .Where(_database.Like(x => x.Username, $"%{query}%")) + ) .Take(20) .ToListAsync(); } diff --git a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs index d452eeb0..fb2a5f28 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -129,7 +129,7 @@ namespace Kyoo.Core.Api { ICollection resources = await Repository.GetAll( ApiHelper.ParseWhere(where), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); diff --git a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs b/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs index 6d788cd8..1b966f44 100644 --- a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs @@ -88,7 +88,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Genres)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); diff --git a/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs b/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs index 61187855..9f5ab352 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs @@ -93,7 +93,7 @@ namespace Kyoo.Core.Api try { Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = new(sortBy); + Sort sort = Sort.From(sortBy); Pagination pagination = new(limit, afterID); ICollection resources = await identifier.Match( diff --git a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs index 9d743853..1feb79af 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs @@ -88,7 +88,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioID, x => x.Studio.Slug)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); diff --git a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs index a3c85aa6..32e49f2b 100644 --- a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs @@ -92,7 +92,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -136,7 +136,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); diff --git a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs index 317b382e..9241b26f 100644 --- a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs @@ -160,7 +160,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.Matcher(x => x.EpisodeID, x => x.Episode.Slug)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID)); if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs index c6b785de..ed4b42b7 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs @@ -91,7 +91,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -135,7 +135,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -181,7 +181,7 @@ namespace Kyoo.Core.Api try { Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = new(sortBy); + Sort sort = Sort.From(sortBy); Pagination pagination = new(limit, afterID); ICollection resources = await identifier.Match( diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs index 5d068fd5..4dc9851e 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs @@ -89,7 +89,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryItems.GetAll( ApiHelper.ParseWhere(where), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); diff --git a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs index fd5e1da1..f71e5c57 100644 --- a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs @@ -92,7 +92,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID)); if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs index bd7569d9..e5ad20b6 100644 --- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -96,7 +96,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -140,7 +140,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -183,7 +183,7 @@ namespace Kyoo.Core.Api try { Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = new(sortBy); + Sort sort = Sort.From(sortBy); Pagination pagination = new(limit, afterID); ICollection resources = await identifier.Match( @@ -232,7 +232,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -298,7 +298,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) ); @@ -342,7 +342,7 @@ namespace Kyoo.Core.Api { ICollection resources = await _libraryManager.GetAll( ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - new Sort(sortBy), + Sort.From(sortBy), new Pagination(limit, afterID) );