Rework filters completly

This commit is contained in:
Zoe Roux 2023-11-22 20:02:35 +01:00
parent e8351e960d
commit e9aaa184cf
30 changed files with 259 additions and 376 deletions

View File

@ -61,11 +61,11 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Get the first resource that match the predicate.
/// </summary>
/// <param name="where">A predicate to filter the resource.</param>
/// <param name="filter">A predicate to filter the resource.</param>
/// <param name="include">The related fields to include.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns>
Task<T> Get(Expression<Func<T, bool>> where, Include<T>? include = default);
Task<T> Get(Filter<T> filter, Include<T>? include = default);
/// <summary>
/// Get a resource from it's ID or null if it is not found.
@ -86,11 +86,11 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Get the first resource that match the predicate or null if it is not found.
/// </summary>
/// <param name="where">A predicate to filter the resource.</param>
/// <param name="filter">A predicate to filter the resource.</param>
/// <param name="include">The related fields to include.</param>
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
/// <returns>The resource found</returns>
Task<T?> GetOrDefault(Expression<Func<T, bool>> where,
Task<T?> GetOrDefault(Filter<T>? filter,
Include<T>? include = default,
Sort<T>? sortBy = default);
@ -105,22 +105,22 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Get every resources that match all filters
/// </summary>
/// <param name="where">A filter predicate</param>
/// <param name="filter">A filter predicate</param>
/// <param name="sort">Sort information about the query (sort by, sort order)</param>
/// <param name="limit">How pagination should be done (where to start and how many to return)</param>
/// <param name="include">The related fields to include.</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>
Task<ICollection<T>> GetAll(Expression<Func<T, bool>>? where = null,
Task<ICollection<T>> GetAll(Filter<T>? filter = null,
Sort<T>? sort = default,
Pagination? limit = default,
Include<T>? include = default);
Include<T>? include = default,
Pagination limit = default);
/// <summary>
/// Get the number of resources that match the filter's predicate.
/// </summary>
/// <param name="where">A filter predicate</param>
/// <param name="filter">A filter predicate</param>
/// <returns>How many resources matched that filter</returns>
Task<int> GetCount(Expression<Func<T, bool>>? where = null);
Task<int> GetCount(Filter<T>? filter = null);
/// <summary>
/// Map a list of ids to a list of items (keep the order).
@ -217,9 +217,9 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Delete all resources that match the predicate.
/// </summary>
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
/// <param name="filter">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task DeleteAll(Expression<Func<T, bool>> where);
Task DeleteAll(Filter<T> filter);
/// <summary>
/// Called when a resource has been edited.

View File

@ -0,0 +1,69 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Kyoo.Abstractions.Models.Utils;
public abstract record Filter
{
public static Filter<T>? And<T>(params Filter<T>?[] filters)
{
return filters
.Where(x => x != null)
.Aggregate((Filter<T>?)null, (acc, filter) =>
{
if (acc == null)
return filter;
return new Filter<T>.And(acc, filter!);
});
}
}
public abstract record Filter<T> : Filter
{
public record And(Filter<T> first, Filter<T> second) : Filter<T>;
public record Or(Filter<T> first, Filter<T> second) : Filter<T>;
public record Not(Filter<T> filter) : Filter<T>;
public record Eq(string property, object value) : Filter<T>;
public record Ne<T2>(string property, T2 value) : Filter<T>;
public record Gt<T2>(string property, T2 value) : Filter<T>;
public record Ge<T2>(string property, T2 value) : Filter<T>;
public record Lt<T2>(string property, T2 value) : Filter<T>;
public record Le<T2>(string property, T2 value) : Filter<T>;
public record Has<T2>(string property, T2 value) : Filter<T>;
public record In(string property, object[] value) : Filter<T>;
public record Lambda(Expression<Func<T, bool>> lambda) : Filter<T>;
public static Filter<T> From(string filter)
{
}
}

View File

@ -99,13 +99,14 @@ namespace Kyoo.Abstractions.Models.Utils
/// identifier.Matcher&lt;Season&gt;(x => x.ShowID, x => x.Show.Slug)
/// </code>
/// </example>
public Expression<Func<T, bool>> Matcher<T>(Expression<Func<T, int>> idGetter,
public Filter<T> Matcher<T>(Expression<Func<T, int>> idGetter,
Expression<Func<T, string>> slugGetter)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self);
ICollection<ParameterExpression> parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters;
return Expression.Lambda<Func<T, bool>>(equal, parameters);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equal, parameters);
return new Filter<T>.Lambda(lambda);
}
/// <summary>
@ -117,13 +118,14 @@ namespace Kyoo.Abstractions.Models.Utils
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The type to match against this identifier.</typeparam>
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
public Expression<Func<T, bool>> Matcher<T>(Expression<Func<T, int?>> idGetter,
public Filter<T> Matcher<T>(Expression<Func<T, int?>> idGetter,
Expression<Func<T, string>> slugGetter)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self);
ICollection<ParameterExpression> parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters;
return Expression.Lambda<Func<T, bool>>(equal, parameters);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(equal, parameters);
return new Filter<T>.Lambda(lambda);
}
/// <summary>
@ -142,13 +144,21 @@ namespace Kyoo.Abstractions.Models.Utils
}
/// <summary>
/// Return an expression that return true if this <see cref="Identifier"/> match a given resource.
/// Return a filter to get this <see cref="Identifier"/> match a given resource.
/// </summary>
/// <typeparam name="T">The type of resource to match against.</typeparam>
/// <returns>
/// <c>true</c> if the given resource match this identifier, <c>false</c> otherwise.
/// </returns>
public Expression<Func<T, bool>> IsSame<T>()
public Filter<T> IsSame<T>()
where T : IResource
{
return _id.HasValue
? new Filter<T>.Eq("Id", _id.Value)
: new Filter<T>.Eq("Slug", _slug!);
}
private Expression<Func<T, bool>> _IsSameExpression<T>()
where T : IResource
{
return _id.HasValue
@ -163,7 +173,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// <typeparam name="T">The type that contain the list to check.</typeparam>
/// <typeparam name="T2">The type of resource to check this identifier against.</typeparam>
/// <returns>An expression to check if this <see cref="Identifier"/> is contained.</returns>
public Expression<Func<T, bool>> IsContainedIn<T, T2>(Expression<Func<T, IEnumerable<T2>>> listGetter)
public Filter<T> IsContainedIn<T, T2>(Expression<Func<T, IEnumerable<T2>?>> listGetter)
where T2 : IResource
{
MethodInfo method = typeof(Enumerable)
@ -171,8 +181,9 @@ namespace Kyoo.Abstractions.Models.Utils
.Where(x => x.Name == nameof(Enumerable.Any))
.FirstOrDefault(x => x.GetParameters().Length == 2)!
.MakeGenericMethod(typeof(T2));
MethodCallExpression call = Expression.Call(null, method, listGetter.Body, IsSame<T2>());
return Expression.Lambda<Func<T, bool>>(call, listGetter.Parameters);
MethodCallExpression call = Expression.Call(null, method, listGetter.Body, _IsSameExpression<T2>());
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, listGetter.Parameters);
return new Filter<T>.Lambda(lambda);
}
/// <inheritdoc />

View File

@ -21,7 +21,7 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Information about the pagination. How many items should be displayed and where to start.
/// </summary>
public class Pagination
public struct Pagination
{
/// <summary>
/// The count of items to return.

View File

@ -1,125 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Reflection;
namespace Kyoo.Utils
{
/// <summary>
/// Static class containing MethodOf calls.
/// </summary>
public static class MethodOfUtils
{
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf(Action action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the action.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T>(Action<T> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the action.</typeparam>
/// <typeparam name="T2">The second parameter of the action.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T, T2>(Action<T, T2> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the action.</typeparam>
/// <typeparam name="T2">The second parameter of the action.</typeparam>
/// <typeparam name="T3">The third parameter of the action.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T, T2, T3>(Action<T, T2, T3> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The return type of function.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T>(Func<T> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the function.</typeparam>
/// <typeparam name="T2">The return type of function.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T, T2>(Func<T, T2> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the function.</typeparam>
/// <typeparam name="T2">The second parameter of the function.</typeparam>
/// <typeparam name="T3">The return type of function.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T, T2, T3>(Func<T, T2, T3> action)
{
return action.Method;
}
/// <summary>
/// Get a MethodInfo from a direct method.
/// </summary>
/// <param name="action">The method (without any arguments or return value.</param>
/// <typeparam name="T">The first parameter of the function.</typeparam>
/// <typeparam name="T2">The second parameter of the function.</typeparam>
/// <typeparam name="T3">The third parameter of the function.</typeparam>
/// <typeparam name="T4">The return type of function.</typeparam>
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
public static MethodInfo MethodOf<T, T2, T3, T4>(Func<T, T2, T3, T4> action)
{
return action.Method;
}
}
}

View File

@ -1,52 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Threading.Tasks;
namespace Kyoo.Utils
{
/// <summary>
/// A class containing helper method for tasks.
/// </summary>
public static class TaskUtils
{
/// <summary>
/// Run a method after the execution of the task.
/// </summary>
/// <param name="task">The task to wait.</param>
/// <param name="then">
/// The method to run after the task finish. This will only be run if the task finished successfully.
/// </param>
/// <typeparam name="T">The type of the item in the task.</typeparam>
/// <returns>A continuation task wrapping the initial task and adding a continuation method.</returns>
/// <exception cref="TaskCanceledException">The source task has been canceled.</exception>
public static Task<T> Then<T>(this Task<T> task, Action<T> then)
{
return task.ContinueWith(x =>
{
if (x.IsFaulted)
x.Exception!.InnerException!.ReThrow();
if (x.IsCanceled)
throw new TaskCanceledException();
then(x.Result);
return x.Result;
}, TaskContinuationOptions.ExecuteSynchronously);
}
}
}

View File

@ -342,18 +342,5 @@ namespace Kyoo.Utils
return string.Empty;
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
}
/// <summary>
/// Rethrow the exception without modifying the stack trace.
/// This is similar to the <c>rethrow;</c> code but is useful when the exception is not in a catch block.
/// </summary>
/// <param name="ex">The exception to rethrow.</param>
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
public static void ReThrow(this Exception ex)
{
if (ex == null)
throw new ArgumentNullException(nameof(ex));
ExceptionDispatchInfo.Capture(ex).Throw();
}
}
}

View File

@ -96,7 +96,7 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<JwtToken>> Login([FromBody] LoginRequest request)
{
User? user = await _users.GetOrDefault(x => x.Username == request.Username);
User? user = await _users.GetOrDefault(new Filter<User>.Eq(nameof(Abstractions.Models.User.Username), request.Username));
if (user == null || !BCryptNet.Verify(request.Password, user.Password))
return Forbid(new RequestError("The user and password does not match."));
@ -126,7 +126,7 @@ namespace Kyoo.Authentication.Views
User user = request.ToUser();
user.Permissions = _permissions.NewUser;
// If no users exists, the new one will be an admin. Give it every permissions.
if (await _users.GetOrDefault(where: x => true) == null)
if (await _users.GetOrDefault(1) == null)
user.Permissions = PermissionOption.Admin;
try
{

View File

@ -53,7 +53,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Collection>> Search(string query, Include<Collection>? include = default)
{
return await AddIncludes(_database.Collections, include)
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -79,7 +79,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Episode>> Search(string query, Include<Episode>? include = default)
{
return await AddIncludes(_database.Episodes, include)
.Where(_database.Like<Episode>(x => x.Name!, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20)
.ToListAsync();
}
@ -92,8 +92,8 @@ namespace Kyoo.Core.Controllers
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() =>
obj is { SeasonNumber: not null, EpisodeNumber: not null }
? Get(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber && x.EpisodeNumber == obj.EpisodeNumber)
: Get(x => x.ShowId == obj.ShowId && x.AbsoluteNumber == obj.AbsoluteNumber));
? _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber && x.EpisodeNumber == obj.EpisodeNumber)
: _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.AbsoluteNumber == obj.AbsoluteNumber));
await IRepository<Episode>.OnResourceCreated(obj);
return obj;
}

View File

@ -29,10 +29,8 @@ using System.Text;
using System.Threading.Tasks;
using Dapper;
using InterpolatedSql.Dapper;
using InterpolatedSql.SqlBuilders;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Utils;
@ -72,11 +70,10 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task<ILibraryItem> Get(
Expression<Func<ILibraryItem, bool>> where,
public virtual async Task<ILibraryItem> Get(Filter<ILibraryItem> filter,
Include<ILibraryItem>? include = default)
{
ILibraryItem? ret = await GetOrDefault(where, include: include);
ILibraryItem? ret = await GetOrDefault(filter, include: include);
if (ret == null)
throw new ItemNotFoundException($"No {nameof(ILibraryItem)} found with the given predicate.");
return ret;
@ -92,7 +89,8 @@ namespace Kyoo.Core.Controllers
throw new NotImplementedException();
}
public Task<ILibraryItem?> GetOrDefault(Expression<Func<ILibraryItem, bool>> where, Include<ILibraryItem>? include = null, Sort<ILibraryItem>? sortBy = null)
public Task<ILibraryItem?> GetOrDefault(Filter<ILibraryItem>? filter, Include<ILibraryItem>? include = default,
Sort<ILibraryItem>? sortBy = default)
{
throw new NotImplementedException();
}
@ -108,9 +106,11 @@ namespace Kyoo.Core.Controllers
return $"coalesce({string.Join(", ", keys)})";
}
public static string ProcessSort<T>(Sort<T> sort, Dictionary<string, Type> config, bool recurse = false)
public static string ProcessSort<T>(Sort<T>? sort, Dictionary<string, Type> config, bool recurse = false)
where T : IQuery
{
sort ??= new Sort<T>.Default();
string ret = sort switch
{
Sort<T>.Default(var value) => ProcessSort(value, config, true),
@ -188,12 +188,24 @@ namespace Kyoo.Core.Controllers
return $"{prefix}*" + projStr;
}
public async Task<ICollection<ILibraryItem>> GetAll(
Expression<Func<ILibraryItem, bool>>? where = null,
Sort<ILibraryItem>? sort = null,
Pagination? limit = null,
Include<ILibraryItem>? include = null)
public static string ProcessFilter<T>(Filter<T> filter, Dictionary<string, Type> config)
{
return filter switch
{
Filter<T>.And(var first, var second) => $"({ProcessFilter(first, config)} and {ProcessFilter(second, config)})",
Filter<T>.Or(var first, var second) => $"({ProcessFilter(first, config)} or {ProcessFilter(second, config)})",
Filter<T>.Not(var inner) => $"(not {ProcessFilter(inner, config)})",
Filter<T>.Eq(var property, var value) => $"({_Property(property, config)} = {value})",
};
}
public async Task<ICollection<ILibraryItem>> GetAll(Filter<ILibraryItem>? filter = null,
Sort<ILibraryItem>? sort = default,
Include<ILibraryItem>? include = default,
Pagination limit = default)
{
include ??= new();
Dictionary<string, Type> config = new()
{
{ "s", typeof(Show) },
@ -203,7 +215,7 @@ namespace Kyoo.Core.Controllers
var (includeConfig, includeJoin, mapIncludes) = ProcessInclude(include, config);
// language=PostgreSQL
IDapperSqlCommand query = _database.SqlBuilder($"""
var query = _database.SqlBuilder($"""
select
{ExpendProjections<Show>("s", include):raw},
m.*,
@ -224,7 +236,10 @@ namespace Kyoo.Core.Controllers
{includeJoin:raw}
order by {ProcessSort(sort, config):raw}
limit {limit.Limit}
""").Build();
""");
if (filter != null)
query += $"where {ProcessFilter(filter, config):raw}";
Type[] types = config.Select(x => x.Value)
.Concat(includeConfig.Select(x => x.Value))
@ -242,7 +257,7 @@ namespace Kyoo.Core.Controllers
return data.ToList();
}
public Task<int> GetCount(Expression<Func<ILibraryItem, bool>>? where = null)
public Task<int> GetCount(Filter<ILibraryItem>? filter = null)
{
throw new NotImplementedException();
}
@ -252,7 +267,7 @@ namespace Kyoo.Core.Controllers
throw new NotImplementedException();
}
public Task DeleteAll(Expression<Func<ILibraryItem, bool>> where)
public Task DeleteAll(Filter<ILibraryItem> filter)
{
throw new NotImplementedException();
}

View File

@ -113,6 +113,11 @@ namespace Kyoo.Core.Controllers
return _Sort(query, sortBy, false).ThenBy(x => x.Id);
}
protected static Expression<Func<T, bool>> ParseFilter(Filter<T>? filter)
{
throw new NotImplementedException();
}
private static Func<Expression, Expression, BinaryExpression> _GetComparisonExpression(
bool desc,
bool next,
@ -297,9 +302,9 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task<T> Get(Expression<Func<T, bool>> where, Include<T>? include = default)
public virtual async Task<T> Get(Filter<T> filter, Include<T>? include = default)
{
T? ret = await GetOrDefault(where, include: include);
T? ret = await GetOrDefault(filter, include: include);
if (ret == null)
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
return ret;
@ -326,7 +331,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public virtual Task<T?> GetOrDefault(Expression<Func<T, bool>> where,
public virtual Task<T?> GetOrDefault(Filter<T>? filter,
Include<T>? include = default,
Sort<T>? sortBy = default)
{
@ -334,7 +339,7 @@ namespace Kyoo.Core.Controllers
AddIncludes(Database.Set<T>(), include),
sortBy
)
.FirstOrDefaultAsync(where);
.FirstOrDefaultAsync(ParseFilter(filter));
}
/// <inheritdoc/>
@ -353,12 +358,12 @@ namespace Kyoo.Core.Controllers
public abstract Task<ICollection<T>> Search(string query, Include<T>? include = default);
/// <inheritdoc/>
public virtual Task<ICollection<T>> GetAll(Expression<Func<T, bool>>? where = null,
public virtual Task<ICollection<T>> GetAll(Filter<T>? filter = null,
Sort<T>? sort = default,
Pagination? limit = default,
Include<T>? include = default)
Include<T>? include = default,
Pagination limit = default)
{
return ApplyFilters(Database.Set<T>(), where, sort, limit, include);
return ApplyFilters(Database.Set<T>(), ParseFilter(filter), sort, limit, include);
}
/// <summary>
@ -373,7 +378,7 @@ namespace Kyoo.Core.Controllers
protected async Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
Expression<Func<T, bool>>? where = null,
Sort<T>? sort = default,
Pagination? limit = default,
Pagination limit = default,
Include<T>? include = default)
{
query = AddIncludes(query, include);
@ -381,25 +386,25 @@ namespace Kyoo.Core.Controllers
if (where != null)
query = query.Where(where);
if (limit?.AfterID != null)
if (limit.AfterID != null)
{
T reference = await Get(limit.AfterID.Value);
query = query.Where(KeysetPaginate(sort, reference, !limit.Reverse));
}
if (limit?.Reverse == true)
if (limit.Reverse)
query = query.Reverse();
if (limit?.Limit > 0)
if (limit.Limit > 0)
query = query.Take(limit.Limit);
return await query.ToListAsync();
}
/// <inheritdoc/>
public virtual Task<int> GetCount(Expression<Func<T, bool>>? where = null)
public virtual Task<int> GetCount(Filter<T>? filter = null)
{
IQueryable<T> query = Database.Set<T>();
if (where != null)
query = query.Where(where);
if (filter != null)
query = query.Where(ParseFilter(filter));
return query.CountAsync();
}
@ -559,9 +564,9 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public async Task DeleteAll(Expression<Func<T, bool>> where)
public async Task DeleteAll(Filter<T> filter)
{
foreach (T resource in await GetAll(where))
foreach (T resource in await GetAll(filter))
await Delete(resource);
}
}

View File

@ -69,7 +69,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Movie>> Search(string query, Include<Movie>? include = default)
{
return await AddIncludes(_database.Movies, include)
.Where(_database.Like<Movie>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -63,7 +63,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<People>> Search(string query, Include<People>? include = default)
{
return await AddIncludes(_database.People, include)
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -74,7 +74,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Season>> Search(string query, Include<Season>? include = default)
{
return await AddIncludes(_database.Seasons, include)
.Where(_database.Like<Season>(x => x.Name!, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name!, $"%{query}%"))
.Take(20)
.ToListAsync();
}
@ -85,7 +85,9 @@ namespace Kyoo.Core.Controllers
await base.Create(obj);
obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug;
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber));
await _database.SaveChangesAsync(() =>
_database.Seasons.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber)
);
await IRepository<Season>.OnResourceCreated(obj);
return obj;
}

View File

@ -70,7 +70,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Show>> Search(string query, Include<Show>? include = default)
{
return await AddIncludes(_database.Shows, include)
.Where(_database.Like<Show>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}
@ -88,8 +88,6 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
protected override async Task Validate(Show resource)
{
resource.Slug ??= Utility.ToSlug(resource.Name);
await base.Validate(resource);
if (resource.Studio != null)
{

View File

@ -53,7 +53,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<Studio>> Search(string query, Include<Studio>? include = default)
{
return await AddIncludes(_database.Studios, include)
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
.Take(20)
.ToListAsync();
}
@ -68,13 +68,6 @@ namespace Kyoo.Core.Controllers
return obj;
}
/// <inheritdoc />
protected override async Task Validate(Studio resource)
{
resource.Slug ??= Utility.ToSlug(resource.Name);
await base.Validate(resource);
}
/// <inheritdoc />
public override async Task Delete(Studio obj)
{

View File

@ -52,7 +52,7 @@ namespace Kyoo.Core.Controllers
public override async Task<ICollection<User>> Search(string query, Include<User>? include = default)
{
return await AddIncludes(_database.Users, include)
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
.Where(x => EF.Functions.ILike(x.Username, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -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;
@ -86,16 +85,16 @@ namespace Kyoo.Core.Api
/// <remarks>
/// Get the number of resources that match the filters.
/// </remarks>
/// <param name="where">A list of filters to respect.</param>
/// <param name="filter">A list of filters to respect.</param>
/// <returns>How many resources matched that filter.</returns>
/// <response code="400">Invalid filters.</response>
[HttpGet("count")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
public async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
public async Task<ActionResult<int>> GetCount([FromQuery] Filter<T> filter)
{
return await Repository.GetCount(ApiHelper.ParseWhere<T>(where));
return await Repository.GetCount(filter);
}
/// <summary>
@ -105,7 +104,7 @@ namespace Kyoo.Core.Api
/// Get all resources that match the given filter.
/// </remarks>
/// <param name="sortBy">Sort information about the query (sort by, sort order).</param>
/// <param name="where">Filter the returned items.</param>
/// <param name="filter">Filter the returned items.</param>
/// <param name="pagination">How many items per page should be returned, where should the page start...</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A list of resources that match every filters.</returns>
@ -116,15 +115,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
public async Task<ActionResult<Page<T>>> GetAll(
[FromQuery] Sort<T> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<T>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<T>? fields)
{
ICollection<T> resources = await Repository.GetAll(
ApiHelper.ParseWhere<T>(where),
filter,
sortBy,
pagination,
fields
fields,
pagination
);
return Page(resources, pagination.Limit);
@ -231,20 +230,19 @@ namespace Kyoo.Core.Api
/// <remarks>
/// Delete all items matching the given filters. If no filter is specified, delete all items.
/// </remarks>
/// <param name="where">The list of filters.</param>
/// <param name="filter">The list of filters.</param>
/// <returns>The item(s) has successfully been deleted.</returns>
/// <response code="400">One or multiple filters are invalid.</response>
[HttpDelete]
[PartialPermission(Kind.Delete)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
public async Task<IActionResult> Delete([FromQuery] Dictionary<string, string> where)
public async Task<IActionResult> Delete([FromQuery] Filter<T> filter)
{
Expression<Func<T, bool>>? w = ApiHelper.ParseWhere<T>(where);
if (w == null)
if (filter == null)
return BadRequest(new RequestError("Incule a filter to delete items, all items won't be deleted."));
await Repository.DeleteAll(w);
await Repository.DeleteAll(filter);
return NoContent();
}
}

View File

@ -65,7 +65,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Studio"/>.</param>
/// <param name="sortBy">A key to sort shows by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of shows to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of shows.</returns>
@ -79,15 +79,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
[FromQuery] Sort<Show> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Show>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Show> fields)
{
ICollection<Show> resources = await _libraryManager.Shows.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)),
Filter.And(filter, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Studios.GetOrDefault(identifier.IsSame<Studio>()) == null)

View File

@ -126,7 +126,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
/// <param name="sortBy">A key to sort items by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of items to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of items.</returns>
@ -140,21 +140,21 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<ILibraryItem>>> GetItems(Identifier identifier,
[FromQuery] Sort<ILibraryItem> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<ILibraryItem>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<ILibraryItem>? fields)
{
ICollection<ILibraryItem> resources = await _items.GetAllOfCollection(
identifier.IsSame<Collection>(),
ApiHelper.ParseWhere<ILibraryItem>(where),
sortBy == new Sort<ILibraryItem>.Default() ? new Sort<ILibraryItem>.By(nameof(Movie.AirDate)) : sortBy,
pagination,
fields
);
// ICollection<ILibraryItem> resources = await _items.GetAllOfCollection(
// identifier.IsSame<Collection>(),
// filter,
// sortBy == new Sort<ILibraryItem>.Default() ? new Sort<ILibraryItem>.By(nameof(Movie.AirDate)) : sortBy,
// pagination,
// fields
// );
if (!resources.Any() && await _libraryManager.Collections.GetOrDefault(identifier.IsSame<Collection>()) == null)
// if (!resources.Any() && await _libraryManager.Collections.GetOrDefault(identifier.IsSame<Collection>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
// return Page(resources, pagination.Limit);
}
/// <summary>
@ -165,9 +165,9 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
/// <param name="sortBy">A key to sort shows by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of shows to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <param name="fields">The additional fields to include in the result.</param>
/// <returns>A page of shows.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No collection with the given ID could be found.</response>
@ -179,15 +179,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
[FromQuery] Sort<Show> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Show>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Show>? fields)
{
ICollection<Show> resources = await _libraryManager.Shows.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections!)),
Filter.And(filter, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
sortBy == new Sort<Show>.Default() ? new Sort<Show>.By(x => x.AirDate) : sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Collections.GetOrDefault(identifier.IsSame<Collection>()) == null)
@ -203,7 +203,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
/// <param name="sortBy">A key to sort movies by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of movies to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of movies.</returns>
@ -217,15 +217,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Movie>>> GetMovies(Identifier identifier,
[FromQuery] Sort<Movie> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Movie>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Movie>? fields)
{
ICollection<Movie> resources = await _libraryManager.Movies.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Movie, Collection>(x => x.Collections!)),
Filter.And(filter, identifier.IsContainedIn<Movie, Collection>(x => x.Collections)),
sortBy == new Sort<Movie>.Default() ? new Sort<Movie>.By(x => x.AirDate) : sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Collections.GetOrDefault(identifier.IsSame<Collection>()) == null)

View File

@ -120,7 +120,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort collections by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of collections to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of collections.</returns>
@ -134,15 +134,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
[FromQuery] Sort<Collection> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Collection>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Collection> fields)
{
ICollection<Collection> resources = await _libraryManager.Collections.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies!)),
Filter.And(filter, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Movies.GetOrDefault(identifier.IsSame<Movie>()) == null)

View File

@ -16,13 +16,10 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Core.Controllers;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
@ -37,25 +34,10 @@ namespace Kyoo.Core.Api
[ResourceView]
[PartialPermission("LibraryItem")]
[ApiDefinition("News", Group = ResourcesGroup)]
public class NewsApi : BaseApi
public class NewsApi : CrudThumbsApi<News>
{
private readonly NewsRepository _news;
public NewsApi(NewsRepository news)
{
_news = news;
}
public async Task<ActionResult<Page<News>>> GetAll(
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<News> resources = await _news.GetAll(
ApiHelper.ParseWhere<News>(where),
limit: pagination
);
return Page(resources, pagination.Limit);
}
public NewsApi(IRepository<News> news, IThumbnailsManager thumbs)
: base(news, thumbs)
{ }
}
}

View File

@ -67,7 +67,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Season"/>.</param>
/// <param name="sortBy">A key to sort episodes by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of episodes to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of episodes.</returns>
@ -81,15 +81,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Episode>>> GetEpisode(Identifier identifier,
[FromQuery] Sort<Episode> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Episode>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Episode> fields)
{
ICollection<Episode> resources = await _libraryManager.Episodes.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)),
Filter.And(filter, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Seasons.GetOrDefault(identifier.IsSame<Season>()) == null)

View File

@ -67,7 +67,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort seasons by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of seasons to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of seasons.</returns>
@ -81,15 +81,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Season>>> GetSeasons(Identifier identifier,
[FromQuery] Sort<Season> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Season>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Season> fields)
{
ICollection<Season> resources = await _libraryManager.Seasons.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)),
Filter.And(filter, identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Shows.GetOrDefault(identifier.IsSame<Show>()) == null)
@ -105,7 +105,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort episodes by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of episodes to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of episodes.</returns>
@ -119,15 +119,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Episode>>> GetEpisodes(Identifier identifier,
[FromQuery] Sort<Episode> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Episode>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Episode> fields)
{
ICollection<Episode> resources = await _libraryManager.Episodes.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)),
Filter.And(filter, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Shows.GetOrDefault(identifier.IsSame<Show>()) == null)
@ -197,7 +197,7 @@ namespace Kyoo.Core.Api
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort collections by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="filter">An optional list of filters.</param>
/// <param name="pagination">The number of collections to return.</param>
/// <param name="fields">The aditional fields to include in the result.</param>
/// <returns>A page of collections.</returns>
@ -211,15 +211,15 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
[FromQuery] Sort<Collection> sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Filter<Collection>? filter,
[FromQuery] Pagination pagination,
[FromQuery] Include<Collection> fields)
{
ICollection<Collection> resources = await _libraryManager.Collections.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
Filter.And(filter, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
sortBy,
pagination,
fields
fields,
pagination
);
if (!resources.Any() && await _libraryManager.Shows.GetOrDefault(identifier.IsSame<Show>()) == null)

View File

@ -541,14 +541,5 @@ namespace Kyoo.Postgresql
entry.State = EntityState.Detached;
}
}
/// <summary>
/// Perform a case insensitive like operation.
/// </summary>
/// <param name="query">An accessor to get the item that will be checked.</param>
/// <param name="format">The second operator of the like format.</param>
/// <typeparam name="T">The type of the item to query</typeparam>
/// <returns>An expression representing the like query. It can directly be passed to a where call.</returns>
public abstract Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> query, string format);
}
}

View File

@ -139,14 +139,5 @@ namespace Kyoo.Postgresql
{
return ex.InnerException is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation };
}
/// <inheritdoc />
public override Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> query, string format)
{
MethodInfo iLike = MethodOfUtils.MethodOf<string, string, bool>(EF.Functions.ILike);
MethodCallExpression call = Expression.Call(iLike, Expression.Constant(EF.Functions), query.Body, Expression.Constant(format));
return Expression.Lambda<Func<T, bool>>(call, query.Parameters);
}
}
}

View File

@ -1,3 +1,21 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Data;
using System.Linq;

View File

@ -89,7 +89,7 @@ namespace Kyoo.Tests.Database
}
public IRepository<T> GetRepository<T>()
where T : class, IResource
where T : class, IResource, IQuery
{
return _repositories.First(x => x.RepositoryType == typeof(T)) as IRepository<T>;
}

View File

@ -29,7 +29,7 @@ using Xunit;
namespace Kyoo.Tests.Database
{
public abstract class RepositoryTests<T> : IDisposable, IAsyncDisposable
where T : class, IResource
where T : class, IResource, IQuery
{
protected readonly RepositoryActivator Repositories;
private readonly IRepository<T> _repository;