diff --git a/Kyoo.CommonAPI/ApiHelper.cs b/Kyoo.CommonAPI/ApiHelper.cs index 7486b163..8258cbe6 100644 --- a/Kyoo.CommonAPI/ApiHelper.cs +++ b/Kyoo.CommonAPI/ApiHelper.cs @@ -20,13 +20,14 @@ namespace Kyoo.CommonApi return operand(left, right); } - public static Expression> ParseWhere(Dictionary where) + public static Expression> ParseWhere(Dictionary where, + Expression> defaultWhere = null) { if (where == null || where.Count == 0) return null; ParameterExpression param = Expression.Parameter(typeof(T)); - Expression expression = null; + Expression expression = defaultWhere?.Body; foreach ((string key, string desired) in where) { @@ -42,21 +43,26 @@ namespace Kyoo.CommonApi if (property == null) throw new ArgumentException($"No filterable parameter with the name {key}."); MemberExpression propertyExpr = Expression.Property(param, property); - - Type propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; - object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase) - ? null - : Convert.ChangeType(value, propertyType); - ConstantExpression valueExpr = Expression.Constant(val, property.PropertyType); + + ConstantExpression valueExpr = null; + if (operand != "ctn") + { + Type propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; + object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase) + ? null + : Convert.ChangeType(value, propertyType); + valueExpr = Expression.Constant(val, property.PropertyType); + } Expression condition = operand switch { - "eq" => Expression.Equal(propertyExpr, valueExpr), - "not" => Expression.NotEqual(propertyExpr, valueExpr), + "eq" => Expression.Equal(propertyExpr, valueExpr!), + "not" => Expression.NotEqual(propertyExpr, valueExpr!), "lt" => StringCompatibleExpression(Expression.LessThan, propertyExpr, valueExpr), "lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr), "gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr), "gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr), + "ctn" => ContainsResourceExpression(propertyExpr, value), _ => throw new ArgumentException($"Invalid operand: {operand}") }; @@ -68,5 +74,37 @@ namespace Kyoo.CommonApi return Expression.Lambda>(expression!, param); } + + private static Expression ContainsResourceExpression(MemberExpression xProperty, string value) + { + // x => x.PROPERTY.Any(y => y.Slug == value) + Expression ret = null; + ParameterExpression y = Expression.Parameter(xProperty.Type.GenericTypeArguments.First(), "y"); + foreach (string val in value.Split(',')) + { + MemberExpression yProperty; + ConstantExpression yValue; + if (int.TryParse(val, out int id)) + { + yProperty = Expression.Property(y, "ID"); + yValue = Expression.Constant(id); + } + else + { + yProperty = Expression.Property(y, "Slug"); + yValue = Expression.Constant(val); + } + + LambdaExpression lambda = Expression.Lambda(Expression.Equal(yProperty, yValue), y); + Expression iteration = Expression.Call(typeof(Enumerable), "Any", xProperty.Type.GenericTypeArguments, + xProperty, lambda); + + if (ret == null) + ret = iteration; + else + ret = Expression.AndAlso(ret, iteration); + } + return ret; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/GenresAPI.cs b/Kyoo/Views/API/GenresAPI.cs index 118c4fcc..f0ded1e3 100644 --- a/Kyoo/Views/API/GenresAPI.cs +++ b/Kyoo/Views/API/GenresAPI.cs @@ -1,27 +1,92 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; namespace Kyoo.Api { - [Route("api/genres")] [Route("api/genre")] + [Route("api/genres")] [ApiController] - public class GenresAPI : ControllerBase + public class GenresAPI : CrudApi { private readonly ILibraryManager _libraryManager; - public GenresAPI(ILibraryManager libraryManager) + public GenresAPI(ILibraryManager libraryManager, IConfiguration config) + : base(libraryManager.GenreRepository, config) { _libraryManager = libraryManager; } - public async Task>> Index() + [HttpGet("{id:int}/show")] + [HttpGet("{id:int}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) { - return (await _libraryManager.GetGenres()).ToList(); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.ID == id)), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/show")] + [HttpGet("{slug}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.Slug == slug)), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } } } } \ No newline at end of file