mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-31 02:27:11 -04:00 
			
		
		
		
	Implementing a basic query parser for where, sort & pagination
This commit is contained in:
		
							parent
							
								
									3443e102e9
								
							
						
					
					
						commit
						ea625fa45b
					
				| @ -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 | ||||
|  | ||||
| @ -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!); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -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) | ||||
|  | ||||
| @ -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}")] | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user