mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-11-03 19:17:16 -05:00 
			
		
		
		
	Add issues api
This commit is contained in:
		
							parent
							
								
									050b420f9a
								
							
						
					
					
						commit
						9f003189e9
					
				
							
								
								
									
										35
									
								
								back/src/Kyoo.Abstractions/Controllers/IIssueRepository.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								back/src/Kyoo.Abstractions/Controllers/IIssueRepository.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					// 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.Threading.Tasks;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Kyoo.Abstractions.Controllers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public interface IIssueRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						Task<ICollection<Issue>> GetAll(Filter<Issue>? filter = default);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Task<int> GetCount(Filter<Issue>? filter = default);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Task<Issue> Upsert(Issue issue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Task DeleteAll(Filter<Issue>? filter = default);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -45,7 +45,7 @@ public class Issue : IAddedDate
 | 
				
			|||||||
	/// <summary>
 | 
						/// <summary>
 | 
				
			||||||
	/// Some extra data that could store domain-specific info.
 | 
						/// Some extra data that could store domain-specific info.
 | 
				
			||||||
	/// </summary>
 | 
						/// </summary>
 | 
				
			||||||
	public Dictionary<string, object> Extra { get; set; }
 | 
						public Dictionary<string, object> Extra { get; set; } = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// <inheritdoc/>
 | 
						/// <inheritdoc/>
 | 
				
			||||||
	public DateTime AddedDate { get; set; }
 | 
						public DateTime AddedDate { get; set; }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										140
									
								
								back/src/Kyoo.Core/Controllers/Repositories/EfHelpers.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								back/src/Kyoo.Core/Controllers/Repositories/EfHelpers.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,140 @@
 | 
				
			|||||||
 | 
					// 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;
 | 
				
			||||||
 | 
					using System.Reflection;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Utils;
 | 
				
			||||||
 | 
					using Kyoo.Postgresql;
 | 
				
			||||||
 | 
					using Kyoo.Utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Kyoo.Core.Controllers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public static class EfHelpers
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public static Expression<Func<T, bool>> ToEfLambda<T>(this Filter<T>? filter)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (filter == null)
 | 
				
			||||||
 | 
								return x => true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ParameterExpression x = Expression.Parameter(typeof(T), "x");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expression CmpRandomHandler(string cmp, string seed, Guid refId)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								MethodInfo concat = typeof(string).GetMethod(
 | 
				
			||||||
 | 
									nameof(string.Concat),
 | 
				
			||||||
 | 
									new[] { typeof(string), typeof(string) }
 | 
				
			||||||
 | 
								)!;
 | 
				
			||||||
 | 
								Expression id = Expression.Call(
 | 
				
			||||||
 | 
									Expression.Property(x, "ID"),
 | 
				
			||||||
 | 
									nameof(Guid.ToString),
 | 
				
			||||||
 | 
									null
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								Expression xrng = Expression.Call(concat, Expression.Constant(seed), id);
 | 
				
			||||||
 | 
								Expression left = Expression.Call(
 | 
				
			||||||
 | 
									typeof(DatabaseContext),
 | 
				
			||||||
 | 
									nameof(DatabaseContext.MD5),
 | 
				
			||||||
 | 
									null,
 | 
				
			||||||
 | 
									xrng
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								Expression right = Expression.Call(
 | 
				
			||||||
 | 
									typeof(DatabaseContext),
 | 
				
			||||||
 | 
									nameof(DatabaseContext.MD5),
 | 
				
			||||||
 | 
									null,
 | 
				
			||||||
 | 
									Expression.Constant($"{seed}{refId}")
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								return cmp switch
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"=" => Expression.Equal(left, right),
 | 
				
			||||||
 | 
									"<" => Expression.GreaterThan(left, right),
 | 
				
			||||||
 | 
									">" => Expression.LessThan(left, right),
 | 
				
			||||||
 | 
									_ => throw new NotImplementedException()
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							BinaryExpression StringCompatibleExpression(
 | 
				
			||||||
 | 
								Func<Expression, Expression, BinaryExpression> operand,
 | 
				
			||||||
 | 
								string property,
 | 
				
			||||||
 | 
								object value
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var left = Expression.Property(x, property);
 | 
				
			||||||
 | 
								var right = Expression.Constant(value, ((PropertyInfo)left.Member).PropertyType);
 | 
				
			||||||
 | 
								if (left.Type != typeof(string))
 | 
				
			||||||
 | 
									return operand(left, right);
 | 
				
			||||||
 | 
								MethodCallExpression call = Expression.Call(
 | 
				
			||||||
 | 
									typeof(string),
 | 
				
			||||||
 | 
									"Compare",
 | 
				
			||||||
 | 
									null,
 | 
				
			||||||
 | 
									left,
 | 
				
			||||||
 | 
									right
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								return operand(call, Expression.Constant(0));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expression Exp(
 | 
				
			||||||
 | 
								Func<Expression, Expression, BinaryExpression> operand,
 | 
				
			||||||
 | 
								string property,
 | 
				
			||||||
 | 
								object? value
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								var prop = Expression.Property(x, property);
 | 
				
			||||||
 | 
								var val = Expression.Constant(value, ((PropertyInfo)prop.Member).PropertyType);
 | 
				
			||||||
 | 
								return operand(prop, val);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expression Parse(Filter<T> f)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								return f switch
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Filter<T>.And(var first, var second)
 | 
				
			||||||
 | 
										=> Expression.AndAlso(Parse(first), Parse(second)),
 | 
				
			||||||
 | 
									Filter<T>.Or(var first, var second)
 | 
				
			||||||
 | 
										=> Expression.OrElse(Parse(first), Parse(second)),
 | 
				
			||||||
 | 
									Filter<T>.Not(var inner) => Expression.Not(Parse(inner)),
 | 
				
			||||||
 | 
									Filter<T>.Eq(var property, var value) => Exp(Expression.Equal, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Ne(var property, var value) => Exp(Expression.NotEqual, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Gt(var property, var value)
 | 
				
			||||||
 | 
										=> StringCompatibleExpression(Expression.GreaterThan, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Ge(var property, var value)
 | 
				
			||||||
 | 
										=> StringCompatibleExpression(Expression.GreaterThanOrEqual, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Lt(var property, var value)
 | 
				
			||||||
 | 
										=> StringCompatibleExpression(Expression.LessThan, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Le(var property, var value)
 | 
				
			||||||
 | 
										=> StringCompatibleExpression(Expression.LessThanOrEqual, property, value),
 | 
				
			||||||
 | 
									Filter<T>.Has(var property, var value)
 | 
				
			||||||
 | 
										=> Expression.Call(
 | 
				
			||||||
 | 
											typeof(Enumerable),
 | 
				
			||||||
 | 
											"Contains",
 | 
				
			||||||
 | 
											new[] { value.GetType() },
 | 
				
			||||||
 | 
											Expression.Property(x, property),
 | 
				
			||||||
 | 
											Expression.Constant(value)
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
									Filter<T>.CmpRandom(var op, var seed, var refId)
 | 
				
			||||||
 | 
										=> CmpRandomHandler(op, seed, refId),
 | 
				
			||||||
 | 
									Filter<T>.Lambda(var lambda)
 | 
				
			||||||
 | 
										=> ExpressionArgumentReplacer.ReplaceParams(lambda.Body, lambda.Parameters, x),
 | 
				
			||||||
 | 
									_ => throw new NotImplementedException(),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Expression body = Parse(filter);
 | 
				
			||||||
 | 
							return Expression.Lambda<Func<T, bool>>(body, x);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					// 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.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Controllers;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Utils;
 | 
				
			||||||
 | 
					using Kyoo.Postgresql;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Kyoo.Core.Controllers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class IssueRepository(DatabaseContext database) : IIssueRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						public async Task<ICollection<Issue>> GetAll(Filter<Issue>? filter = null)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return await database.Issues.Where(filter.ToEfLambda()).ToListAsync();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Task<int> GetCount(Filter<Issue>? filter = null)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return database.Issues.Where(filter.ToEfLambda()).CountAsync();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async Task<Issue> Upsert(Issue issue)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							issue.AddedDate = DateTime.UtcNow;
 | 
				
			||||||
 | 
							await database.Issues.Upsert(issue).RunAsync();
 | 
				
			||||||
 | 
							return issue;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Task DeleteAll(Filter<Issue>? filter = null)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return database.Issues.Where(filter.ToEfLambda()).ExecuteDeleteAsync();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -118,125 +118,6 @@ namespace Kyoo.Core.Controllers
 | 
				
			|||||||
			return _Sort(query, sortBy, false).ThenBy(x => x.Id);
 | 
								return _Sort(query, sortBy, false).ThenBy(x => x.Id);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		protected Expression<Func<T, bool>> ParseFilter(Filter<T>? filter)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			if (filter == null)
 | 
					 | 
				
			||||||
				return x => true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			ParameterExpression x = Expression.Parameter(typeof(T), "x");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Expression CmpRandomHandler(string cmp, string seed, Guid refId)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				MethodInfo concat = typeof(string).GetMethod(
 | 
					 | 
				
			||||||
					nameof(string.Concat),
 | 
					 | 
				
			||||||
					new[] { typeof(string), typeof(string) }
 | 
					 | 
				
			||||||
				)!;
 | 
					 | 
				
			||||||
				Expression id = Expression.Call(
 | 
					 | 
				
			||||||
					Expression.Property(x, "ID"),
 | 
					 | 
				
			||||||
					nameof(Guid.ToString),
 | 
					 | 
				
			||||||
					null
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				Expression xrng = Expression.Call(concat, Expression.Constant(seed), id);
 | 
					 | 
				
			||||||
				Expression left = Expression.Call(
 | 
					 | 
				
			||||||
					typeof(DatabaseContext),
 | 
					 | 
				
			||||||
					nameof(DatabaseContext.MD5),
 | 
					 | 
				
			||||||
					null,
 | 
					 | 
				
			||||||
					xrng
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				Expression right = Expression.Call(
 | 
					 | 
				
			||||||
					typeof(DatabaseContext),
 | 
					 | 
				
			||||||
					nameof(DatabaseContext.MD5),
 | 
					 | 
				
			||||||
					null,
 | 
					 | 
				
			||||||
					Expression.Constant($"{seed}{refId}")
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				return cmp switch
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					"=" => Expression.Equal(left, right),
 | 
					 | 
				
			||||||
					"<" => Expression.GreaterThan(left, right),
 | 
					 | 
				
			||||||
					">" => Expression.LessThan(left, right),
 | 
					 | 
				
			||||||
					_ => throw new NotImplementedException()
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			BinaryExpression StringCompatibleExpression(
 | 
					 | 
				
			||||||
				Func<Expression, Expression, BinaryExpression> operand,
 | 
					 | 
				
			||||||
				string property,
 | 
					 | 
				
			||||||
				object value
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				var left = Expression.Property(x, property);
 | 
					 | 
				
			||||||
				var right = Expression.Constant(value, ((PropertyInfo)left.Member).PropertyType);
 | 
					 | 
				
			||||||
				if (left.Type != typeof(string))
 | 
					 | 
				
			||||||
					return operand(left, right);
 | 
					 | 
				
			||||||
				MethodCallExpression call = Expression.Call(
 | 
					 | 
				
			||||||
					typeof(string),
 | 
					 | 
				
			||||||
					"Compare",
 | 
					 | 
				
			||||||
					null,
 | 
					 | 
				
			||||||
					left,
 | 
					 | 
				
			||||||
					right
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				return operand(call, Expression.Constant(0));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Expression Exp(
 | 
					 | 
				
			||||||
				Func<Expression, Expression, BinaryExpression> operand,
 | 
					 | 
				
			||||||
				string property,
 | 
					 | 
				
			||||||
				object? value
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				var prop = Expression.Property(x, property);
 | 
					 | 
				
			||||||
				var val = Expression.Constant(value, ((PropertyInfo)prop.Member).PropertyType);
 | 
					 | 
				
			||||||
				return operand(prop, val);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Expression Parse(Filter<T> f)
 | 
					 | 
				
			||||||
			{
 | 
					 | 
				
			||||||
				return f switch
 | 
					 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					Filter<T>.And(var first, var second)
 | 
					 | 
				
			||||||
						=> Expression.AndAlso(Parse(first), Parse(second)),
 | 
					 | 
				
			||||||
					Filter<T>.Or(var first, var second)
 | 
					 | 
				
			||||||
						=> Expression.OrElse(Parse(first), Parse(second)),
 | 
					 | 
				
			||||||
					Filter<T>.Not(var inner) => Expression.Not(Parse(inner)),
 | 
					 | 
				
			||||||
					Filter<T>.Eq(var property, var value) => Exp(Expression.Equal, property, value),
 | 
					 | 
				
			||||||
					Filter<T>.Ne(var property, var value)
 | 
					 | 
				
			||||||
						=> Exp(Expression.NotEqual, property, value),
 | 
					 | 
				
			||||||
					Filter<T>.Gt(var property, var value)
 | 
					 | 
				
			||||||
						=> StringCompatibleExpression(Expression.GreaterThan, property, value),
 | 
					 | 
				
			||||||
					Filter<T>.Ge(var property, var value)
 | 
					 | 
				
			||||||
						=> StringCompatibleExpression(
 | 
					 | 
				
			||||||
							Expression.GreaterThanOrEqual,
 | 
					 | 
				
			||||||
							property,
 | 
					 | 
				
			||||||
							value
 | 
					 | 
				
			||||||
						),
 | 
					 | 
				
			||||||
					Filter<T>.Lt(var property, var value)
 | 
					 | 
				
			||||||
						=> StringCompatibleExpression(Expression.LessThan, property, value),
 | 
					 | 
				
			||||||
					Filter<T>.Le(var property, var value)
 | 
					 | 
				
			||||||
						=> StringCompatibleExpression(Expression.LessThanOrEqual, property, value),
 | 
					 | 
				
			||||||
					Filter<T>.Has(var property, var value)
 | 
					 | 
				
			||||||
						=> Expression.Call(
 | 
					 | 
				
			||||||
							typeof(Enumerable),
 | 
					 | 
				
			||||||
							"Contains",
 | 
					 | 
				
			||||||
							new[] { value.GetType() },
 | 
					 | 
				
			||||||
							Expression.Property(x, property),
 | 
					 | 
				
			||||||
							Expression.Constant(value)
 | 
					 | 
				
			||||||
						),
 | 
					 | 
				
			||||||
					Filter<T>.CmpRandom(var op, var seed, var refId)
 | 
					 | 
				
			||||||
						=> CmpRandomHandler(op, seed, refId),
 | 
					 | 
				
			||||||
					Filter<T>.Lambda(var lambda)
 | 
					 | 
				
			||||||
						=> ExpressionArgumentReplacer.ReplaceParams(
 | 
					 | 
				
			||||||
							lambda.Body,
 | 
					 | 
				
			||||||
							lambda.Parameters,
 | 
					 | 
				
			||||||
							x
 | 
					 | 
				
			||||||
						),
 | 
					 | 
				
			||||||
					_ => throw new NotImplementedException(),
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			Expression body = Parse(filter);
 | 
					 | 
				
			||||||
			return Expression.Lambda<Func<T, bool>>(body, x);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		protected IQueryable<T> AddIncludes(IQueryable<T> query, Include<T>? include)
 | 
							protected IQueryable<T> AddIncludes(IQueryable<T> query, Include<T>? include)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			if (include == null)
 | 
								if (include == null)
 | 
				
			||||||
@ -405,7 +286,7 @@ namespace Kyoo.Core.Controllers
 | 
				
			|||||||
				filter = Filter.And(filter, keysetFilter);
 | 
									filter = Filter.And(filter, keysetFilter);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if (filter != null)
 | 
								if (filter != null)
 | 
				
			||||||
				query = query.Where(ParseFilter(filter));
 | 
									query = query.Where(filter.ToEfLambda());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (limit.Reverse)
 | 
								if (limit.Reverse)
 | 
				
			||||||
				query = query.Reverse();
 | 
									query = query.Reverse();
 | 
				
			||||||
@ -422,7 +303,7 @@ namespace Kyoo.Core.Controllers
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			IQueryable<T> query = Database.Set<T>();
 | 
								IQueryable<T> query = Database.Set<T>();
 | 
				
			||||||
			if (filter != null)
 | 
								if (filter != null)
 | 
				
			||||||
				query = query.Where(ParseFilter(filter));
 | 
									query = query.Where(filter.ToEfLambda());
 | 
				
			||||||
			return query.CountAsync();
 | 
								return query.CountAsync();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -75,6 +75,11 @@ namespace Kyoo.Core
 | 
				
			|||||||
				.As<IWatchStatusRepository>()
 | 
									.As<IWatchStatusRepository>()
 | 
				
			||||||
				.AsSelf()
 | 
									.AsSelf()
 | 
				
			||||||
				.InstancePerLifetimeScope();
 | 
									.InstancePerLifetimeScope();
 | 
				
			||||||
 | 
								builder
 | 
				
			||||||
 | 
									.RegisterType<IssueRepository>()
 | 
				
			||||||
 | 
									.As<IIssueRepository>()
 | 
				
			||||||
 | 
									.AsSelf()
 | 
				
			||||||
 | 
									.InstancePerLifetimeScope();
 | 
				
			||||||
			builder.RegisterType<SqlVariableContext>().InstancePerLifetimeScope();
 | 
								builder.RegisterType<SqlVariableContext>().InstancePerLifetimeScope();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										119
									
								
								back/src/Kyoo.Core/Views/Metadata/IssueApi.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								back/src/Kyoo.Core/Views/Metadata/IssueApi.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					// 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.Threading.Tasks;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Controllers;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Attributes;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Permissions;
 | 
				
			||||||
 | 
					using Kyoo.Abstractions.Models.Utils;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using static Kyoo.Abstractions.Models.Utils.Constants;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Kyoo.Core.Api;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// <summary>
 | 
				
			||||||
 | 
					/// Create or list issues on the instance
 | 
				
			||||||
 | 
					/// </summary>
 | 
				
			||||||
 | 
					[Route("issues")]
 | 
				
			||||||
 | 
					[Route("issue", Order = AlternativeRoute)]
 | 
				
			||||||
 | 
					[ApiController]
 | 
				
			||||||
 | 
					[PartialPermission(nameof(Issue), Group = Group.Admin)]
 | 
				
			||||||
 | 
					[ApiDefinition("Issue", Group = AdminGroup)]
 | 
				
			||||||
 | 
					public class IssueApi(IIssueRepository issues) : Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						/// <summary>
 | 
				
			||||||
 | 
						/// Get count
 | 
				
			||||||
 | 
						/// </summary>
 | 
				
			||||||
 | 
						/// <remarks>
 | 
				
			||||||
 | 
						/// Get the number of issues that match the filters.
 | 
				
			||||||
 | 
						/// </remarks>
 | 
				
			||||||
 | 
						/// <param name="filter">A list of filters to respect.</param>
 | 
				
			||||||
 | 
						/// <returns>How many issues 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] Filter<Issue> filter)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return await issues.GetCount(filter);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// <summary>
 | 
				
			||||||
 | 
						/// Get all issues
 | 
				
			||||||
 | 
						/// </summary>
 | 
				
			||||||
 | 
						/// <remarks>
 | 
				
			||||||
 | 
						/// Get all issues that match the given filter.
 | 
				
			||||||
 | 
						/// </remarks>
 | 
				
			||||||
 | 
						/// <param name="filter">Filter the returned items.</param>
 | 
				
			||||||
 | 
						/// <returns>A list of issues that match every filters.</returns>
 | 
				
			||||||
 | 
						/// <response code="400">Invalid filters or sort information.</response>
 | 
				
			||||||
 | 
						[HttpGet]
 | 
				
			||||||
 | 
						[PartialPermission(Kind.Read)]
 | 
				
			||||||
 | 
						[ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
						[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
 | 
				
			||||||
 | 
						public async Task<ActionResult<ICollection<Issue>>> GetAll([FromQuery] Filter<Issue>? filter)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return Ok(await issues.GetAll(filter));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// <summary>
 | 
				
			||||||
 | 
						/// Upsert issue
 | 
				
			||||||
 | 
						/// </summary>
 | 
				
			||||||
 | 
						/// <remarks>
 | 
				
			||||||
 | 
						/// Create or update an issue.
 | 
				
			||||||
 | 
						/// </remarks>
 | 
				
			||||||
 | 
						/// <param name="issue">The issue to create.</param>
 | 
				
			||||||
 | 
						/// <returns>The created issue.</returns>
 | 
				
			||||||
 | 
						/// <response code="400">The issue in the request body is invalid.</response>
 | 
				
			||||||
 | 
						[HttpPost]
 | 
				
			||||||
 | 
						[PartialPermission(Kind.Create)]
 | 
				
			||||||
 | 
						[ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
						[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
 | 
				
			||||||
 | 
						public async Task<ActionResult<Issue>> Create([FromBody] Issue issue)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return await issues.Upsert(issue);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// <summary>
 | 
				
			||||||
 | 
						/// Delete issues
 | 
				
			||||||
 | 
						/// </summary>
 | 
				
			||||||
 | 
						/// <remarks>
 | 
				
			||||||
 | 
						/// Delete all issues matching the given filters.
 | 
				
			||||||
 | 
						/// </remarks>
 | 
				
			||||||
 | 
						/// <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] Filter<Issue> filter)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							if (filter == null)
 | 
				
			||||||
 | 
								return BadRequest(
 | 
				
			||||||
 | 
									new RequestError("Incule a filter to delete items, all items won't be deleted.")
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await issues.DeleteAll(filter);
 | 
				
			||||||
 | 
							return NoContent();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -39,7 +39,7 @@ namespace Kyoo.Core.Api;
 | 
				
			|||||||
[PartialPermission(nameof(User), Group = Group.Admin)]
 | 
					[PartialPermission(nameof(User), Group = Group.Admin)]
 | 
				
			||||||
[ApiDefinition("Users", Group = ResourcesGroup)]
 | 
					[ApiDefinition("Users", Group = ResourcesGroup)]
 | 
				
			||||||
public class UserApi(ILibraryManager libraryManager, IThumbnailsManager thumbs)
 | 
					public class UserApi(ILibraryManager libraryManager, IThumbnailsManager thumbs)
 | 
				
			||||||
	: CrudApi<User>(libraryManager.Users)
 | 
						: CrudApi<User>(libraryManager!.Users)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	/// <summary>
 | 
						/// <summary>
 | 
				
			||||||
	/// Get profile picture
 | 
						/// Get profile picture
 | 
				
			||||||
 | 
				
			|||||||
@ -409,9 +409,7 @@ namespace Kyoo.Postgresql
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			modelBuilder.Entity<Movie>().Ignore(x => x.Links);
 | 
								modelBuilder.Entity<Movie>().Ignore(x => x.Links);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								modelBuilder.Entity<Issue>().HasKey(x => new { x.Domain, x.Cause });
 | 
				
			||||||
			modelBuilder.Entity<Issue>()
 | 
					 | 
				
			||||||
				.HasKey(x => new { x.Domain, x.Cause });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// TODO: Waiting for https://github.com/dotnet/efcore/issues/29825
 | 
								// TODO: Waiting for https://github.com/dotnet/efcore/issues/29825
 | 
				
			||||||
			// modelBuilder.Entity<T>()
 | 
								// modelBuilder.Entity<T>()
 | 
				
			||||||
@ -419,7 +417,8 @@ namespace Kyoo.Postgresql
 | 
				
			|||||||
			// 	{
 | 
								// 	{
 | 
				
			||||||
			// 		x.ToJson();
 | 
								// 		x.ToJson();
 | 
				
			||||||
			// 	});
 | 
								// 	});
 | 
				
			||||||
			modelBuilder.Entity<Issue>()
 | 
								modelBuilder
 | 
				
			||||||
 | 
									.Entity<Issue>()
 | 
				
			||||||
				.Property(x => x.Extra)
 | 
									.Property(x => x.Extra)
 | 
				
			||||||
				.HasConversion(
 | 
									.HasConversion(
 | 
				
			||||||
					v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
 | 
										v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,8 @@ namespace Kyoo.Postgresql.Migrations
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			migrationBuilder.DropForeignKey(
 | 
								migrationBuilder.DropForeignKey(
 | 
				
			||||||
				name: "fk_show_watch_status_episodes_next_episode_id",
 | 
									name: "fk_show_watch_status_episodes_next_episode_id",
 | 
				
			||||||
				table: "show_watch_status");
 | 
									table: "show_watch_status"
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			migrationBuilder.CreateTable(
 | 
								migrationBuilder.CreateTable(
 | 
				
			||||||
				name: "issues",
 | 
									name: "issues",
 | 
				
			||||||
@ -23,12 +24,17 @@ namespace Kyoo.Postgresql.Migrations
 | 
				
			|||||||
					cause = table.Column<string>(type: "text", nullable: false),
 | 
										cause = table.Column<string>(type: "text", nullable: false),
 | 
				
			||||||
					reason = table.Column<string>(type: "text", nullable: false),
 | 
										reason = table.Column<string>(type: "text", nullable: false),
 | 
				
			||||||
					extra = table.Column<string>(type: "json", nullable: false),
 | 
										extra = table.Column<string>(type: "json", nullable: false),
 | 
				
			||||||
					added_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'")
 | 
										added_date = table.Column<DateTime>(
 | 
				
			||||||
 | 
											type: "timestamp with time zone",
 | 
				
			||||||
 | 
											nullable: false,
 | 
				
			||||||
 | 
											defaultValueSql: "now() at time zone 'utc'"
 | 
				
			||||||
 | 
										)
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				constraints: table =>
 | 
									constraints: table =>
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					table.PrimaryKey("pk_issues", x => new { x.domain, x.cause });
 | 
										table.PrimaryKey("pk_issues", x => new { x.domain, x.cause });
 | 
				
			||||||
				});
 | 
									}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			migrationBuilder.AddForeignKey(
 | 
								migrationBuilder.AddForeignKey(
 | 
				
			||||||
				name: "fk_show_watch_status_episodes_next_episode_id",
 | 
									name: "fk_show_watch_status_episodes_next_episode_id",
 | 
				
			||||||
@ -36,7 +42,8 @@ namespace Kyoo.Postgresql.Migrations
 | 
				
			|||||||
				column: "next_episode_id",
 | 
									column: "next_episode_id",
 | 
				
			||||||
				principalTable: "episodes",
 | 
									principalTable: "episodes",
 | 
				
			||||||
				principalColumn: "id",
 | 
									principalColumn: "id",
 | 
				
			||||||
				onDelete: ReferentialAction.SetNull);
 | 
									onDelete: ReferentialAction.SetNull
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/// <inheritdoc />
 | 
							/// <inheritdoc />
 | 
				
			||||||
@ -44,17 +51,18 @@ namespace Kyoo.Postgresql.Migrations
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			migrationBuilder.DropForeignKey(
 | 
								migrationBuilder.DropForeignKey(
 | 
				
			||||||
				name: "fk_show_watch_status_episodes_next_episode_id",
 | 
									name: "fk_show_watch_status_episodes_next_episode_id",
 | 
				
			||||||
				table: "show_watch_status");
 | 
									table: "show_watch_status"
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			migrationBuilder.DropTable(
 | 
								migrationBuilder.DropTable(name: "issues");
 | 
				
			||||||
				name: "issues");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			migrationBuilder.AddForeignKey(
 | 
								migrationBuilder.AddForeignKey(
 | 
				
			||||||
				name: "fk_show_watch_status_episodes_next_episode_id",
 | 
									name: "fk_show_watch_status_episodes_next_episode_id",
 | 
				
			||||||
				table: "show_watch_status",
 | 
									table: "show_watch_status",
 | 
				
			||||||
				column: "next_episode_id",
 | 
									column: "next_episode_id",
 | 
				
			||||||
				principalTable: "episodes",
 | 
									principalTable: "episodes",
 | 
				
			||||||
				principalColumn: "id");
 | 
									principalColumn: "id"
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user