Rework sort system to allow multiples keys

This commit is contained in:
Zoe Roux 2023-03-13 01:34:16 +09:00
parent e32c09f48f
commit c0c263c4d7
30 changed files with 464 additions and 489 deletions

View File

@ -16,6 +16,7 @@ dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0046.severity = none dotnet_diagnostic.IDE0046.severity = none
dotnet_diagnostic.CA1848.severity = none dotnet_diagnostic.CA1848.severity = none
dotnet_diagnostic.CA2007.severity = none dotnet_diagnostic.CA2007.severity = none
dotnet_diagnostic.CA1716.severity = none
# Sort using and Import directives with System.* appearing first # Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true dotnet_sort_system_directives_first = true
csharp_using_directive_placement = outside_namespace:warning csharp_using_directive_placement = outside_namespace:warning

View File

@ -19,7 +19,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -256,7 +255,7 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the source object</typeparam> /// <typeparam name="T">The type of the source object</typeparam>
/// <typeparam name="T2">The related resource's type</typeparam> /// <typeparam name="T2">The related resource's type</typeparam>
/// <returns>The param <paramref name="obj"/></returns> /// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member, bool force = false) Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member, bool force = false)
@ -274,7 +273,7 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the source object</typeparam> /// <typeparam name="T">The type of the source object</typeparam>
/// <typeparam name="T2">The related resource's type</typeparam> /// <typeparam name="T2">The related resource's type</typeparam>
/// <returns>The param <paramref name="obj"/></returns> /// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false) Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
@ -291,8 +290,8 @@ namespace Kyoo.Abstractions.Controllers
/// </param> /// </param>
/// <typeparam name="T">The type of the source object</typeparam> /// <typeparam name="T">The type of the source object</typeparam>
/// <returns>The param <paramref name="obj"/></returns> /// <returns>The param <paramref name="obj"/></returns>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T>([NotNull] T obj, string memberName, bool force = false) Task<T> Load<T>([NotNull] T obj, string memberName, bool force = false)
where T : class, IResource; where T : class, IResource;
@ -305,8 +304,8 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="force"> /// <param name="force">
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise. /// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
/// </param> /// </param>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Load([NotNull] IResource obj, string memberName, bool force = false); Task Load([NotNull] IResource obj, string memberName, bool force = false);
@ -325,21 +324,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="id">The ID of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetItemsFromLibrary(id, where, new Sort<LibraryItem>(sort), limit);
/// <summary> /// <summary>
/// Get items (A wrapper around shows or collections) from a library. /// Get items (A wrapper around shows or collections) from a library.
/// </summary> /// </summary>
@ -354,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="slug">The slug of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetItemsFromLibrary(slug, where, new Sort<LibraryItem>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
/// </summary> /// </summary>
@ -383,21 +352,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showID">The ID of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetPeopleFromShow(showID, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
/// </summary> /// </summary>
@ -412,21 +366,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showSlug">The slug of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetPeopleFromShow(showSlug, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
/// </summary> /// </summary>
@ -441,21 +380,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="id">The id of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetRolesFromPeople(id, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
/// </summary> /// </summary>
@ -470,21 +394,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="slug">The slug of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetRolesFromPeople(slug, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Setup relations between a show, a library and a collection /// Setup relations between a show, a library and a collection
/// </summary> /// </summary>
@ -516,22 +425,6 @@ namespace Kyoo.Abstractions.Controllers
Pagination limit = default) Pagination limit = default)
where T : class, IResource; where T : class, IResource;
/// <summary>
/// Get all resources with filters
/// </summary>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by function</param>
/// <param name="limit">How many items to return and where to start</param>
/// <typeparam name="T">The type of resources to load</typeparam>
/// <returns>A list of resources that match every filters</returns>
Task<ICollection<T>> GetAll<T>([Optional] Expression<Func<T, bool>> where,
Expression<Func<T, object>> sort,
Pagination limit = default)
where T : class, IResource
{
return GetAll(where, new Sort<T>(sort), limit);
}
/// <summary> /// <summary>
/// Get the count of resources that match the filter /// Get the count of resources that match the filter
/// </summary> /// </summary>

View File

@ -19,7 +19,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -106,19 +105,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<T> sort = default, Sort<T> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get every resources that match all filters
/// </summary>
/// <param name="where">A filter predicate</param>
/// <param name="sort">A sort by predicate. The order is ascending.</param>
/// <param name="limit">How pagination should be done (where to start and how many to return)</param>
/// <returns>A list of resources that match every filters</returns>
[ItemNotNull]
Task<ICollection<T>> GetAll([Optional] Expression<Func<T, bool>> where,
Expression<Func<T, object>> sort,
Pagination limit = default
) => GetAll(where, new Sort<T>(sort), limit);
/// <summary> /// <summary>
/// Get the number of resources that match the filter's predicate. /// Get the number of resources that match the filter's predicate.
/// </summary> /// </summary>
@ -352,21 +338,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="id">The ID of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetFromLibrary(id, where, new Sort<LibraryItem>(sort), limit);
/// <summary> /// <summary>
/// Get items (A wrapper around shows or collections) from a library. /// Get items (A wrapper around shows or collections) from a library.
/// </summary> /// </summary>
@ -380,21 +351,6 @@ namespace Kyoo.Abstractions.Controllers
Expression<Func<LibraryItem, bool>> where = null, Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default, Sort<LibraryItem> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="slug">The slug of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
[Optional] Expression<Func<LibraryItem, bool>> where,
Expression<Func<LibraryItem, object>> sort,
Pagination limit = default
) => GetFromLibrary(slug, where, new Sort<LibraryItem>(sort), limit);
} }
/// <summary> /// <summary>
@ -431,21 +387,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showID">The ID of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(int showID,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromShow(showID, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
/// </summary> /// </summary>
@ -460,21 +401,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a show.
/// </summary>
/// <param name="showSlug">The slug of the show</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromShow(showSlug, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
/// </summary> /// </summary>
@ -489,21 +415,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="id">The id of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(int id,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromPeople(id, where, new Sort<PeopleRole>(sort), limit);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
/// </summary> /// </summary>
@ -517,21 +428,6 @@ namespace Kyoo.Abstractions.Controllers
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default); Pagination limit = default);
/// <summary>
/// Get people's roles from a person.
/// </summary>
/// <param name="slug">The slug of the person</param>
/// <param name="where">A filter function</param>
/// <param name="sort">A sort by method</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
[Optional] Expression<Func<PeopleRole, bool>> where,
Expression<Func<PeopleRole, object>> sort,
Pagination limit = default
) => GetFromPeople(slug, where, new Sort<PeopleRole>(sort), limit);
} }
/// <summary> /// <summary>
@ -551,21 +447,6 @@ namespace Kyoo.Abstractions.Controllers
Sort<MetadataID> sort = default, Sort<MetadataID> sort = default,
Pagination limit = default) Pagination limit = default)
where T : class, IMetadata; where T : class, IMetadata;
/// <summary>
/// Get a list of external ids that match all filters
/// </summary>
/// <param name="where">A predicate to add arbitrary filter</param>
/// <param name="sort">A sort by expression</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
/// <returns>A filtered list of external ids.</returns>
Task<ICollection<MetadataID>> GetMetadataID<T>([Optional] Expression<Func<MetadataID, bool>> where,
Expression<Func<MetadataID, object>> sort,
Pagination limit = default
)
where T : class, IMetadata
=> GetMetadataID<T>(where, new Sort<MetadataID>(sort), limit);
} }
/// <summary> /// <summary>

View File

@ -113,7 +113,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// <summary> /// <summary>
/// A matcher overload for nullable IDs. See /// A matcher overload for nullable IDs. See
/// <see cref="Matcher{T}(System.Linq.Expressions.Expression{System.Func{T,int}},System.Linq.Expressions.Expression{System.Func{T,string}})"/> /// <see cref="Matcher{T}(Expression{Func{T,int}},Expression{Func{T,string}})"/>
/// for more details. /// for more details.
/// </summary> /// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param> /// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>

View File

@ -33,15 +33,22 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
public int? AfterID { get; } public int? AfterID { get; }
/// <summary>
/// Should the previous page be returned instead of the next?
/// </summary>
public bool Reverse { get; }
/// <summary> /// <summary>
/// Create a new <see cref="Pagination"/> instance. /// Create a new <see cref="Pagination"/> instance.
/// </summary> /// </summary>
/// <param name="count">Set the <see cref="Count"/> value</param> /// <param name="count">Set the <see cref="Count"/> value</param>
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param> /// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
public Pagination(int count, int? afterID = null) /// <param name="reverse">Should the previous page be returned instead of the next?</param>
public Pagination(int count, int? afterID = null, bool reverse = false)
{ {
Count = count; Count = count;
AfterID = afterID; AfterID = afterID;
Reverse = reverse;
} }
/// <summary> /// <summary>

View File

@ -17,7 +17,9 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System; using System;
using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection;
using Kyoo.Utils; using Kyoo.Utils;
namespace Kyoo.Abstractions.Controllers 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. /// Information about how a query should be sorted. What factor should decide the sort and in which order.
/// </summary> /// </summary>
/// <typeparam name="T">For witch type this sort applies</typeparam> /// <typeparam name="T">For witch type this sort applies</typeparam>
public readonly struct Sort<T> public record Sort<T>
{ {
/// <summary> /// <summary>
/// The sort key. This member will be used to sort the results. /// Sort by a specific key
/// </summary> /// </summary>
public Expression<Func<T, object>> Key { get; } /// <param name="key">The sort keys. This members will be used to sort the results.</param>
/// <param name="desendant">
/// <summary>
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order. /// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </summary> /// </param>
public bool Descendant { get; } public record By(string key, bool desendant = false) : Sort<T>
{
/// <summary>
/// Sort by a specific key
/// </summary>
/// <param name="key">The sort keys. This members will be used to sort the results.</param>
/// <param name="desendant">
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </param>
public By(Expression<Func<T, object>> key, bool desendant = false)
: this(Utility.GetPropertyName(key), desendant) { }
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
/// </summary>
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
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);
}
}
/// <summary> /// <summary>
/// Create a new <see cref="Sort{T}"/> instance. /// Sort by multiple keys.
/// </summary> /// </summary>
/// <param name="key">The sort key given. It is assigned to <see cref="Key"/>.</param> /// <param name="list">The list of keys to sort by.</param>
/// <param name="descendant">Should this be in descendant order? The default is false.</param> public record Conglomerate(params By[] list) : Sort<T>;
/// <exception cref="ArgumentException">If the given key is not a member.</exception>
public Sort(Expression<Func<T, object>> key, bool descendant = false)
{
Key = key;
Descendant = descendant;
if (!Utility.IsPropertyExpression(Key)) /// <summary>The default sort method for the given type.</summary>
throw new ArgumentException("The given sort key is not valid."); public record Default : Sort<T>;
}
/// <summary> /// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive). /// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
/// </summary> /// </summary>
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param> /// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception> /// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
public Sort(string sortBy) /// <returns>A <see cref="Sort{T}"/> for the given string</returns>
public static Sort<T> From(string sortBy)
{ {
if (string.IsNullOrEmpty(sortBy)) if (string.IsNullOrEmpty(sortBy))
{ return new Default();
Key = null; if (sortBy.Contains(','))
Descendant = false; return new Conglomerate(sortBy.Split(',').Select(By.From).ToArray());
return; return By.From(sortBy);
}
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<Func<T, object>>(Expression.Convert(property, typeof(object)), param)
: Expression.Lambda<Func<T, object>>(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}.")
};
} }
} }
} }

View File

@ -163,48 +163,46 @@ namespace Kyoo.Abstractions.Models
await library.Load(ep, x => x.Show); await library.Load(ep, x => x.Show);
await library.Load(ep, x => x.Tracks); await library.Load(ep, x => x.Tracks);
Episode previous = null; // if (!ep.Show.IsMovie)
Episode next = null; // {
if (!ep.Show.IsMovie) // if (ep.AbsoluteNumber != null)
{ // {
if (ep.AbsoluteNumber != null) // previous = await library.GetOrDefault(
{ // x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber,
previous = await library.GetOrDefault( // new Sort<Episode>(x => x.AbsoluteNumber, true)
x => x.ShowID == ep.ShowID && x.AbsoluteNumber < ep.AbsoluteNumber, // );
new Sort<Episode>(x => x.AbsoluteNumber, true) // next = await library.GetOrDefault(
); // x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber,
next = await library.GetOrDefault( // new Sort<Episode>(x => x.AbsoluteNumber)
x => x.ShowID == ep.ShowID && x.AbsoluteNumber > ep.AbsoluteNumber, // );
new Sort<Episode>(x => x.AbsoluteNumber) // }
); // else if (ep.SeasonNumber != null && ep.EpisodeNumber != null)
} // {
else if (ep.SeasonNumber != null && ep.EpisodeNumber != null) // previous = await library.GetOrDefault(
{ // x => x.ShowID == ep.ShowID
previous = await library.GetOrDefault( // && x.SeasonNumber == ep.SeasonNumber
x => x.ShowID == ep.ShowID // && x.EpisodeNumber < ep.EpisodeNumber,
&& x.SeasonNumber == ep.SeasonNumber // new Sort<Episode>(x => x.EpisodeNumber, true)
&& x.EpisodeNumber < ep.EpisodeNumber, // );
new Sort<Episode>(x => x.EpisodeNumber, true) // previous ??= await library.GetOrDefault(
); // x => x.ShowID == ep.ShowID
previous ??= await library.GetOrDefault( // && x.SeasonNumber == ep.SeasonNumber - 1,
x => x.ShowID == ep.ShowID // new Sort<Episode>(x => x.EpisodeNumber, true)
&& x.SeasonNumber == ep.SeasonNumber - 1, // );
new Sort<Episode>(x => x.EpisodeNumber, true) //
); // next = await library.GetOrDefault(
// x => x.ShowID == ep.ShowID
next = await library.GetOrDefault( // && x.SeasonNumber == ep.SeasonNumber
x => x.ShowID == ep.ShowID // && x.EpisodeNumber > ep.EpisodeNumber,
&& x.SeasonNumber == ep.SeasonNumber // new Sort<Episode>(x => x.EpisodeNumber)
&& x.EpisodeNumber > ep.EpisodeNumber, // );
new Sort<Episode>(x => x.EpisodeNumber) // next ??= await library.GetOrDefault(
); // x => x.ShowID == ep.ShowID
next ??= await library.GetOrDefault( // && x.SeasonNumber == ep.SeasonNumber + 1,
x => x.ShowID == ep.ShowID // new Sort<Episode>(x => x.EpisodeNumber)
&& x.SeasonNumber == ep.SeasonNumber + 1, // );
new Sort<Episode>(x => x.EpisodeNumber) // }
); // }
}
}
return new WatchItem return new WatchItem
{ {
@ -225,8 +223,12 @@ namespace Kyoo.Abstractions.Models
Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(),
Fonts = await transcoder.ListFonts(ep), Fonts = await transcoder.ListFonts(ep),
PreviousEpisode = previous, PreviousEpisode = ep.Show.IsMovie
NextEpisode = next, ? null
: (await library.GetAll<Episode>(limit: new Pagination(1, ep.ID, true))).FirstOrDefault(),
NextEpisode = ep.Show.IsMovie
? null
: (await library.GetAll<Episode>(limit: new Pagination(1, ep.ID))).FirstOrDefault(),
Chapters = await _GetChapters(ep, fs), Chapters = await _GetChapters(ep, fs),
IsMovie = ep.Show.IsMovie IsMovie = ep.Show.IsMovie
}; };

View File

@ -19,7 +19,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -44,7 +43,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name; protected override Sort<Collection> DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <summary> /// <summary>
/// Create a new <see cref="CollectionRepository"/>. /// Create a new <see cref="CollectionRepository"/>.
@ -61,11 +60,11 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Collection>> Search(string query) public override async Task<ICollection<Collection>> Search(string query)
{ {
return await _database.Collections return await Sort(
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%")) _database.Collections
.OrderBy(DefaultSort) .Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Take(20) .Take(20)
.ToListAsync(); ).ToListAsync();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -19,7 +19,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -51,7 +50,12 @@ namespace Kyoo.Core.Controllers
private readonly ITrackRepository _tracks; private readonly ITrackRepository _tracks;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber; // Use absolute numbers by default and fallback to season/episodes if it does not exists.
protected override Sort<Episode> DefaultSort => new Sort<Episode>.Conglomerate(
new Sort<Episode>.By(x => x.AbsoluteNumber),
new Sort<Episode>.By(x => x.SeasonNumber),
new Sort<Episode>.By(x => x.EpisodeNumber)
);
/// <summary> /// <summary>
/// Create a new <see cref="EpisodeRepository"/>. /// Create a new <see cref="EpisodeRepository"/>.
@ -120,11 +124,12 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Episode>> Search(string query) public override async Task<ICollection<Episode>> Search(string query)
{ {
List<Episode> ret = await _database.Episodes List<Episode> ret = await Sort(
.Include(x => x.Show) _database.Episodes
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) .Include(x => x.Show)
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%")) .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
.OrderBy(DefaultSort) .Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
foreach (Episode ep in ret) foreach (Episode ep in ret)

View File

@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Genre, object>> DefaultSort => x => x.Slug; protected override Sort<Genre> DefaultSort => new Sort<Genre>.By(x => x.Slug);
/// <summary> /// <summary>
/// Create a new <see cref="GenreRepository"/>. /// Create a new <see cref="GenreRepository"/>.
@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Genre>> Search(string query) public override async Task<ICollection<Genre>> Search(string query)
{ {
return await _database.Genres return await Sort(
.Where(_database.Like<Genre>(x => x.Name, $"%{query}%")) _database.Genres
.OrderBy(DefaultSort) .Where(_database.Like<Genre>(x => x.Name, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly Lazy<ILibraryRepository> _libraries; private readonly Lazy<ILibraryRepository> _libraries;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<LibraryItem, object>> DefaultSort => x => x.Title; protected override Sort<LibraryItem> DefaultSort => new Sort<LibraryItem>.By(x => x.Title);
/// <summary> /// <summary>
/// Create a new <see cref="LibraryItemRepository"/>. /// Create a new <see cref="LibraryItemRepository"/>.
@ -92,9 +92,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<LibraryItem>> Search(string query) public override async Task<ICollection<LibraryItem>> Search(string query)
{ {
return await _database.LibraryItems return await Sort(
.Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%")) _database.LibraryItems
.OrderBy(DefaultSort) .Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Library, object>> DefaultSort => x => x.ID; protected override Sort<Library> DefaultSort => new Sort<Library>.By(x => x.ID);
/// <summary> /// <summary>
/// Create a new <see cref="LibraryRepository"/> instance. /// Create a new <see cref="LibraryRepository"/> instance.
@ -62,9 +62,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Library>> Search(string query) public override async Task<ICollection<Library>> Search(string query)
{ {
return await _database.Libraries return await Sort(
.Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%")) _database.Libraries
.OrderBy(DefaultSort) .Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -47,7 +48,7 @@ namespace Kyoo.Core.Controllers
/// <summary> /// <summary>
/// The default sort order that will be used for this resource's type. /// The default sort order that will be used for this resource's type.
/// </summary> /// </summary>
protected abstract Expression<Func<T, object>> DefaultSort { get; } protected abstract Sort<T> DefaultSort { get; }
/// <summary> /// <summary>
/// Create a new base <see cref="LocalRepository{T}"/> with the given database handle. /// Create a new base <see cref="LocalRepository{T}"/> with the given database handle.
@ -61,6 +62,206 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/> /// <inheritdoc/>
public Type RepositoryType => typeof(T); public Type RepositoryType => typeof(T);
/// <summary>
/// Sort the given query.
/// </summary>
/// <param name="query">The query to sort.</param>
/// <param name="sortBy">How to sort the query</param>
/// <returns>The newly sorted query.</returns>
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T> sortBy = null)
{
sortBy ??= DefaultSort;
switch (sortBy)
{
case Sort<T>.Default:
return Sort(query, DefaultSort);
case Sort<T>.By(var key, var desc):
return desc
? query.OrderByDescending(x => EF.Property<object>(x, key))
: query.OrderBy(x => EF.Property<T>(x, key));
case Sort<T>.Conglomerate(var keys):
IOrderedQueryable<T> nQuery = Sort(query, keys[0]);
foreach ((string key, bool desc) in keys.Skip(1))
{
nQuery = desc
? nQuery.ThenByDescending(x => EF.Property<object>(x, key))
: nQuery.ThenBy(x => EF.Property<object>(x, key));
}
return nQuery;
default:
// The language should not require me to do this...
throw new SwitchExpressionException();
}
}
private static Func<Expression, Expression, BinaryExpression> GetComparisonExpression(
bool desc,
bool next,
bool orEqual)
{
bool greaterThan = desc ^ next;
return orEqual
? (greaterThan ? Expression.GreaterThanOrEqual : Expression.LessThanOrEqual)
: (greaterThan ? Expression.GreaterThan : Expression.LessThan);
}
/// <summary>
/// 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 &lt; b) OR
/// (x = a AND y = b AND z > c) OR...
/// </summary>
protected Expression<Func<T, bool>> KeysetPaginatate(
Sort<T> sort,
T reference,
bool next = true)
{
if (sort is Sort<T>.Default)
sort = DefaultSort;
// x =>
ParameterExpression x = Expression.Parameter(typeof(T), "x");
ConstantExpression referenceC = Expression.Constant(reference, typeof(T));
if (sort is Sort<T>.By(var key, var desc))
{
Func<Expression, Expression, BinaryExpression> 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<Func<T, bool>>(compare, x);
}
if (sort is Sort<T>.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<Func<object>> 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<Func<T, bool>>(finalExpression, param);
}
/// <summary> /// <summary>
/// Get a resource from it's ID and make the <see cref="Database"/> instance track it. /// Get a resource from it's ID and make the <see cref="Database"/> instance track it.
/// </summary> /// </summary>
@ -117,10 +318,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default) public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
{ {
IQueryable<T> query = Database.Set<T>(); return Sort(Database.Set<T>(), sortBy).FirstOrDefaultAsync(where);
Expression<Func<T, object>> sortKey = sortBy.Key ?? DefaultSort;
query = sortBy.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
return query.FirstOrDefaultAsync(where);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -142,54 +340,19 @@ namespace Kyoo.Core.Controllers
/// <param name="sort">The sort settings (sort order and sort by)</param> /// <param name="sort">The sort settings (sort order and sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param> /// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <returns>The filtered query</returns> /// <returns>The filtered query</returns>
protected Task<ICollection<T>> ApplyFilters(IQueryable<T> query, protected async Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
Expression<Func<T, bool>> where = null, Expression<Func<T, bool>> where = null,
Sort<T> sort = default, Sort<T> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit); query = Sort(query, sort);
}
/// <summary>
/// Apply filters to a query to ease sort, pagination and where queries for any resources types.
/// For resources of type <typeparamref name="T"/>, see <see cref="ApplyFilters"/>
/// </summary>
/// <param name="query">The base query to filter.</param>
/// <param name="get">A function to asynchronously get a resource from the database using it's ID.</param>
/// <param name="defaultSort">The default sort order of this resource's type.</param>
/// <param name="where">An expression to filter based on arbitrary conditions</param>
/// <param name="sort">The sort settings (sort order and sort by)</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="TValue">The type of items to query.</typeparam>
/// <returns>The filtered query</returns>
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
Func<int, Task<TValue>> get,
Expression<Func<TValue, object>> defaultSort,
Expression<Func<TValue, bool>> where = null,
Sort<TValue> sort = default,
Pagination limit = default)
{
if (where != null) if (where != null)
query = query.Where(where); query = query.Where(where);
Expression<Func<TValue, object>> 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) if (limit.AfterID != null)
{ {
TValue after = await get(limit.AfterID.Value); T reference = await Get(limit.AfterID.Value);
Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type); query = query.Where(KeysetPaginatate(sort, reference));
query = query.Where(Expression.Lambda<Func<TValue, bool>>(
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key),
sortKey.Parameters.First()
));
} }
if (limit.Count > 0) if (limit.Count > 0)
query = query.Take(limit.Count); query = query.Take(limit.Count);

View File

@ -51,7 +51,7 @@ namespace Kyoo.Core.Controllers
private readonly Lazy<IShowRepository> _shows; private readonly Lazy<IShowRepository> _shows;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<People, object>> DefaultSort => x => x.Name; protected override Sort<People> DefaultSort => new Sort<People>.By(x => x.Name);
/// <summary> /// <summary>
/// Create a new <see cref="PeopleRepository"/> /// Create a new <see cref="PeopleRepository"/>
@ -72,9 +72,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<People>> Search(string query) public override async Task<ICollection<People>> Search(string query)
{ {
return await _database.People return await Sort(
.Where(_database.Like<People>(x => x.Name, $"%{query}%")) _database.People
.OrderBy(DefaultSort) .Where(_database.Like<People>(x => x.Name, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
@ -152,19 +153,19 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles throw new NotImplementedException();
.Where(x => x.ShowID == showID) // ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
.Include(x => x.People), // .Where(x => x.ShowID == showID)
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), // .Include(x => x.People),
x => x.People.Name, // x => x.People.Name,
where, // where,
sort, // sort,
limit); // limit);
if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null) // if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null)
throw new ItemNotFoundException(); // throw new ItemNotFoundException();
foreach (PeopleRole role in people) // foreach (PeopleRole role in people)
role.ForPeople = true; // role.ForPeople = true;
return people; // return people;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -173,20 +174,21 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles throw new NotImplementedException();
.Where(x => x.Show.Slug == showSlug) // ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
.Include(x => x.People) // .Where(x => x.Show.Slug == showSlug)
.Include(x => x.Show), // .Include(x => x.People)
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), // .Include(x => x.Show),
x => x.People.Name, // id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
where, // x => x.People.Name,
sort, // where,
limit); // sort,
if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null) // limit);
throw new ItemNotFoundException(); // if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null)
foreach (PeopleRole role in people) // throw new ItemNotFoundException();
role.ForPeople = true; // foreach (PeopleRole role in people)
return people; // role.ForPeople = true;
// return people;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -195,17 +197,18 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles throw new NotImplementedException();
.Where(x => x.PeopleID == id) // ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
.Include(x => x.Show), // .Where(x => x.PeopleID == id)
y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y), // .Include(x => x.Show),
x => x.Show.Title, // y => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == y),
where, // x => x.Show.Title,
sort, // where,
limit); // sort,
if (!roles.Any() && await GetOrDefault(id) == null) // limit);
throw new ItemNotFoundException(); // if (!roles.Any() && await GetOrDefault(id) == null)
return roles; // throw new ItemNotFoundException();
// return roles;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -214,17 +217,18 @@ namespace Kyoo.Core.Controllers
Sort<PeopleRole> sort = default, Sort<PeopleRole> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles throw new NotImplementedException();
.Where(x => x.People.Slug == slug) // ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
.Include(x => x.Show), // .Where(x => x.People.Slug == slug)
id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), // .Include(x => x.Show),
x => x.Show.Title, // id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id),
where, // x => x.Show.Title,
sort, // where,
limit); // sort,
if (!roles.Any() && await GetOrDefault(slug) == null) // limit);
throw new ItemNotFoundException(); // if (!roles.Any() && await GetOrDefault(slug) == null)
return roles; // throw new ItemNotFoundException();
// return roles;
} }
} }
} }

View File

@ -49,14 +49,15 @@ namespace Kyoo.Core.Controllers
} }
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug; protected override Sort<Provider> DefaultSort => new Sort<Provider>.By(x => x.Slug);
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Provider>> Search(string query) public override async Task<ICollection<Provider>> Search(string query)
{ {
return await _database.Providers return await Sort(
.Where(_database.Like<Provider>(x => x.Name, $"%{query}%")) _database.Providers
.OrderBy(DefaultSort) .Where(_database.Like<Provider>(x => x.Name, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }
@ -87,13 +88,14 @@ namespace Kyoo.Core.Controllers
Pagination limit = default) Pagination limit = default)
where T : class, IMetadata where T : class, IMetadata
{ {
return ApplyFilters(_database.MetadataIds<T>() throw new NotImplementedException();
.Include(y => y.Provider), // return ApplyFilters(_database.MetadataIds<T>()
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.ResourceID == x), // .Include(y => y.Provider),
x => x.ResourceID, // x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.ResourceID == x),
where, // x => x.ResourceID,
sort, // where,
limit); // sort,
// limit);
} }
} }
} }

View File

@ -45,7 +45,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
/// <inheritdoc/> /// <inheritdoc/>
protected override Expression<Func<Season, object>> DefaultSort => x => x.SeasonNumber; protected override Sort<Season> DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
/// <summary> /// <summary>
/// Create a new <see cref="SeasonRepository"/>. /// Create a new <see cref="SeasonRepository"/>.
@ -95,9 +95,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/> /// <inheritdoc/>
public override async Task<ICollection<Season>> Search(string query) public override async Task<ICollection<Season>> Search(string query)
{ {
return await _database.Seasons return await Sort(
.Where(_database.Like<Season>(x => x.Title, $"%{query}%")) _database.Seasons
.OrderBy(DefaultSort) .Where(_database.Like<Season>(x => x.Title, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -60,7 +60,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Show, object>> DefaultSort => x => x.Title; protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Title);
/// <summary> /// <summary>
/// Create a new <see cref="ShowRepository"/>. /// Create a new <see cref="ShowRepository"/>.
@ -88,9 +88,10 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Show>> Search(string query) public override async Task<ICollection<Show>> Search(string query)
{ {
query = $"%{query}%"; query = $"%{query}%";
return await _database.Shows return await Sort(
.Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query)) _database.Shows
.OrderBy(DefaultSort) .Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -44,7 +44,7 @@ namespace Kyoo.Core.Controllers
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name; protected override Sort<Studio> DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <summary> /// <summary>
/// Create a new <see cref="StudioRepository"/>. /// Create a new <see cref="StudioRepository"/>.
@ -61,9 +61,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<Studio>> Search(string query) public override async Task<ICollection<Studio>> Search(string query)
{ {
return await _database.Studios return await Sort(
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%")) _database.Studios
.OrderBy(DefaultSort) .Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -18,7 +18,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
@ -38,7 +37,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<Track, object>> DefaultSort => x => x.TrackIndex; protected override Sort<Track> DefaultSort => new Sort<Track>.By(x => x.TrackIndex);
/// <summary> /// <summary>
/// Create a new <see cref="TrackRepository"/>. /// Create a new <see cref="TrackRepository"/>.

View File

@ -39,7 +39,7 @@ namespace Kyoo.Core.Controllers
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
/// <inheritdoc /> /// <inheritdoc />
protected override Expression<Func<User, object>> DefaultSort => x => x.Username; protected override Sort<User> DefaultSort => new Sort<User>.By(x => x.Username);
/// <summary> /// <summary>
/// Create a new <see cref="UserRepository"/> /// Create a new <see cref="UserRepository"/>
@ -54,9 +54,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<User>> Search(string query) public override async Task<ICollection<User>> Search(string query)
{ {
return await _database.Users return await Sort(
.Where(_database.Like<User>(x => x.Username, $"%{query}%")) _database.Users
.OrderBy(DefaultSort) .Where(_database.Like<User>(x => x.Username, $"%{query}%"))
)
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
} }

View File

@ -129,7 +129,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<T> resources = await Repository.GetAll( ICollection<T> resources = await Repository.GetAll(
ApiHelper.ParseWhere<T>(where), ApiHelper.ParseWhere<T>(where),
new Sort<T>(sortBy), Sort<T>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );

View File

@ -88,7 +88,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Show> resources = await _libraryManager.GetAll( ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
new Sort<Show>(sortBy), Sort<Show>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );

View File

@ -93,7 +93,7 @@ namespace Kyoo.Core.Api
try try
{ {
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where); Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = new(sortBy); Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
Pagination pagination = new(limit, afterID); Pagination pagination = new(limit, afterID);
ICollection<PeopleRole> resources = await identifier.Match( ICollection<PeopleRole> resources = await identifier.Match(

View File

@ -88,7 +88,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Show> resources = await _libraryManager.GetAll( ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioID, x => x.Studio.Slug)),
new Sort<Show>(sortBy), Sort<Show>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );

View File

@ -92,7 +92,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Show> resources = await _libraryManager.GetAll( ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
new Sort<Show>(sortBy), Sort<Show>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -136,7 +136,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Library> resources = await _libraryManager.GetAll( ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)),
new Sort<Library>(sortBy), Sort<Library>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );

View File

@ -160,7 +160,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Track> resources = await _libraryManager.GetAll( ICollection<Track> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Track>(x => x.EpisodeID, x => x.Episode.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Track>(x => x.EpisodeID, x => x.Episode.Slug)),
new Sort<Track>(sortBy), Sort<Track>.From(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null)

View File

@ -91,7 +91,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Show> resources = await _libraryManager.GetAll( ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)),
new Sort<Show>(sortBy), Sort<Show>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -135,7 +135,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Collection> resources = await _libraryManager.GetAll( ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)),
new Sort<Collection>(sortBy), Sort<Collection>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -181,7 +181,7 @@ namespace Kyoo.Core.Api
try try
{ {
Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where); Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where);
Sort<LibraryItem> sort = new(sortBy); Sort<LibraryItem> sort = Sort<LibraryItem>.From(sortBy);
Pagination pagination = new(limit, afterID); Pagination pagination = new(limit, afterID);
ICollection<LibraryItem> resources = await identifier.Match( ICollection<LibraryItem> resources = await identifier.Match(

View File

@ -89,7 +89,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<LibraryItem> resources = await _libraryItems.GetAll( ICollection<LibraryItem> resources = await _libraryItems.GetAll(
ApiHelper.ParseWhere<LibraryItem>(where), ApiHelper.ParseWhere<LibraryItem>(where),
new Sort<LibraryItem>(sortBy), Sort<LibraryItem>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );

View File

@ -92,7 +92,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Episode> resources = await _libraryManager.GetAll( ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)),
new Sort<Episode>(sortBy), Sort<Episode>.From(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null)

View File

@ -96,7 +96,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Season> resources = await _libraryManager.GetAll( ICollection<Season> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowID, x => x.Show.Slug)),
new Sort<Season>(sortBy), Sort<Season>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -140,7 +140,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Episode> resources = await _libraryManager.GetAll( ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)), ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowID, x => x.Show.Slug)),
new Sort<Episode>(sortBy), Sort<Episode>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -183,7 +183,7 @@ namespace Kyoo.Core.Api
try try
{ {
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where); Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = new(sortBy); Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
Pagination pagination = new(limit, afterID); Pagination pagination = new(limit, afterID);
ICollection<PeopleRole> resources = await identifier.Match( ICollection<PeopleRole> resources = await identifier.Match(
@ -232,7 +232,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Genre> resources = await _libraryManager.GetAll( ICollection<Genre> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)),
new Sort<Genre>(sortBy), Sort<Genre>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -298,7 +298,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Library> resources = await _libraryManager.GetAll( ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)),
new Sort<Library>(sortBy), Sort<Library>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );
@ -342,7 +342,7 @@ namespace Kyoo.Core.Api
{ {
ICollection<Collection> resources = await _libraryManager.GetAll( ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows)), ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows)),
new Sort<Collection>(sortBy), Sort<Collection>.From(sortBy),
new Pagination(limit, afterID) new Pagination(limit, afterID)
); );