Implementing a basic query parser for where, sort & pagination

This commit is contained in:
Zoe Roux 2020-06-30 00:12:06 +02:00
parent 3443e102e9
commit ea625fa45b
4 changed files with 95 additions and 7 deletions

View File

@ -31,6 +31,27 @@ namespace Kyoo.Controllers
Key = key;
Descendant = descendant;
}
public Sort(string sortBy)
{
if (string.IsNullOrEmpty(sortBy))
{
Key = null;
Descendant = false;
return;
}
string key = sortBy.Contains(':') ? sortBy.Substring(0, sortBy.IndexOf(':')) : sortBy;
string order = sortBy.Contains(':') ? sortBy.Substring(sortBy.IndexOf(':') + 1) : null;
Key = Expression.Lambda<Func<T, object>>(Expression.Property(Expression.Parameter(typeof(T), "x"), key));
Descendant = order switch
{
"desc" => true,
"asc" => false,
_ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
};
}
}
public interface IRepository<T> : IDisposable, IAsyncDisposable

View File

@ -236,5 +236,49 @@ namespace Kyoo
return member.Member.Name;
}
public static Expression<Func<T, bool>> ParseWhere<T>(Dictionary<string, string> where)
{
if (where == null || where.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T));
Expression expression = null;
foreach (KeyValuePair<string, string> cond in where)
{
string value = cond.Value;
string operand = "eq";
if (value.Contains(':'))
{
operand = value.Substring(0, value.IndexOf(':'));
value = value.Substring(value.IndexOf(':') + 1);
}
PropertyInfo valueParam = typeof(T).GetProperty(cond.Key); // TODO get this property with case insensitive.
// TODO throw if valueParam is null.
MemberExpression property = Expression.Property(param, valueParam);
ConstantExpression condValue = Expression.Constant(value); // TODO Cast this to the right type (take nullable into account).
Expression condition = operand switch
{
"eq" => Expression.Equal(property, condValue),
"not" => Expression.NotEqual(property, condValue),
"lt" => Expression.LessThan(property, condValue),
"lte" => Expression.LessThanOrEqual(property, condValue),
"gt" => Expression.GreaterThan(property, condValue),
"gte" => Expression.GreaterThanOrEqual(property, condValue),
"like" => throw new NotImplementedException("Like not implemented yet"),
_ => throw new ArgumentException($"Invalid operand: {operand}")
};
if (expression != null)
expression = Expression.AndAlso(expression, condition);
else
expression = condition;
}
return Expression.Lambda<Func<T, bool>>(expression!);
}
}
}

View File

@ -75,7 +75,25 @@ namespace Kyoo.Controllers
Sort<Show> sort = default,
Pagination page = default)
{
return await _database.Shows.ToListAsync();
IQueryable<Show> query = _database.Shows;
if (where != null)
query = query.Where(where);
Expression<Func<Show, object>> sortKey = sort.Key ?? (x => x.Title);
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
if (page.AfterID != 0)
{
Show after = await Get(page.AfterID);
object afterObj = sortKey.Compile()(after);
query = query.Where(Expression.Lambda<Func<Show, bool>>(
Expression.GreaterThan(sortKey, Expression.Constant(afterObj))
));
}
query = query.Take(page.Count <= 0 ? 20 : page.Count);
return await query.ToListAsync();
}
public async Task<int> Create(Show obj)

View File

@ -1,8 +1,11 @@
using Kyoo.Models;
using System;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Castle.DynamicProxy.Generators.Emitters.SimpleAST;
using Kyoo.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
@ -35,12 +38,14 @@ namespace Kyoo.Api
[HttpGet]
[Authorize(Policy="Read")]
public IEnumerable<Show> GetShows()
public async Task<IEnumerable<Show>> GetShows([FromQuery] string sortBy,
[FromQuery] int limit,
[FromQuery] int afterID,
[FromQuery] Dictionary<string, string> where)
{
return _database.LibraryLinks
.Include(x => x.Show)
.Include(x => x.Collection)
.AsEnumerable().Select(x => x.Show ?? x.Collection.AsShow()).ToList();
return await _libraryManager.GetShows(Utility.ParseWhere<Show>(where),
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
}
[HttpGet("{slug}")]