mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 20:24:27 -04:00
Adding abstract class for repositories & CRUD APIs
This commit is contained in:
parent
b94328688b
commit
2ad4c89806
@ -39,75 +39,75 @@ namespace Kyoo.Controllers
|
|||||||
// Get all
|
// Get all
|
||||||
Task<ICollection<Library>> GetLibraries(Expression<Func<Library, bool>> where = null,
|
Task<ICollection<Library>> GetLibraries(Expression<Func<Library, bool>> where = null,
|
||||||
Sort<Library> sort = default,
|
Sort<Library> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Collection>> GetCollections(Expression<Func<Collection, bool>> where = null,
|
Task<ICollection<Collection>> GetCollections(Expression<Func<Collection, bool>> where = null,
|
||||||
Sort<Collection> sort = default,
|
Sort<Collection> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Show>> GetShows(Expression<Func<Show, bool>> where = null,
|
Task<ICollection<Show>> GetShows(Expression<Func<Show, bool>> where = null,
|
||||||
Sort<Show> sort = default,
|
Sort<Show> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Season>> GetSeasons(Expression<Func<Season, bool>> where = null,
|
Task<ICollection<Season>> GetSeasons(Expression<Func<Season, bool>> where = null,
|
||||||
Sort<Season> sort = default,
|
Sort<Season> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Episode>> GetEpisodes(Expression<Func<Episode, bool>> where = null,
|
Task<ICollection<Episode>> GetEpisodes(Expression<Func<Episode, bool>> where = null,
|
||||||
Sort<Episode> sort = default,
|
Sort<Episode> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Track>> GetTracks(Expression<Func<Track, bool>> where = null,
|
Task<ICollection<Track>> GetTracks(Expression<Func<Track, bool>> where = null,
|
||||||
Sort<Track> sort = default,
|
Sort<Track> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Studio>> GetStudios(Expression<Func<Studio, bool>> where = null,
|
Task<ICollection<Studio>> GetStudios(Expression<Func<Studio, bool>> where = null,
|
||||||
Sort<Studio> sort = default,
|
Sort<Studio> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<People>> GetPeople(Expression<Func<People, bool>> where = null,
|
Task<ICollection<People>> GetPeople(Expression<Func<People, bool>> where = null,
|
||||||
Sort<People> sort = default,
|
Sort<People> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<Genre>> GetGenres(Expression<Func<Genre, bool>> where = null,
|
Task<ICollection<Genre>> GetGenres(Expression<Func<Genre, bool>> where = null,
|
||||||
Sort<Genre> sort = default,
|
Sort<Genre> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
Task<ICollection<ProviderID>> GetProviders(Expression<Func<ProviderID, bool>> where = null,
|
Task<ICollection<ProviderID>> GetProviders(Expression<Func<ProviderID, bool>> where = null,
|
||||||
Sort<ProviderID> sort = default,
|
Sort<ProviderID> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
|
|
||||||
Task<ICollection<Library>> GetLibraries([Optional] Expression<Func<Library, bool>> where,
|
Task<ICollection<Library>> GetLibraries([Optional] Expression<Func<Library, bool>> where,
|
||||||
Expression<Func<Library, object>> sort,
|
Expression<Func<Library, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetLibraries(where, new Sort<Library>(sort), page);
|
) => GetLibraries(where, new Sort<Library>(sort), limit);
|
||||||
Task<ICollection<Collection>> GetCollections([Optional] Expression<Func<Collection, bool>> where,
|
Task<ICollection<Collection>> GetCollections([Optional] Expression<Func<Collection, bool>> where,
|
||||||
Expression<Func<Collection, object>> sort,
|
Expression<Func<Collection, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetCollections(where, new Sort<Collection>(sort), page);
|
) => GetCollections(where, new Sort<Collection>(sort), limit);
|
||||||
Task<ICollection<Show>> GetShows([Optional] Expression<Func<Show, bool>> where,
|
Task<ICollection<Show>> GetShows([Optional] Expression<Func<Show, bool>> where,
|
||||||
Expression<Func<Show, object>> sort,
|
Expression<Func<Show, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetShows(where, new Sort<Show>(sort), page);
|
) => GetShows(where, new Sort<Show>(sort), limit);
|
||||||
Task<ICollection<Season>> GetSeasons([Optional] Expression<Func<Season, bool>> where,
|
Task<ICollection<Season>> GetSeasons([Optional] Expression<Func<Season, bool>> where,
|
||||||
Expression<Func<Season, object>> sort,
|
Expression<Func<Season, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetSeasons(where, new Sort<Season>(sort), page);
|
) => GetSeasons(where, new Sort<Season>(sort), limit);
|
||||||
Task<ICollection<Episode>> GetEpisodes([Optional] Expression<Func<Episode, bool>> where,
|
Task<ICollection<Episode>> GetEpisodes([Optional] Expression<Func<Episode, bool>> where,
|
||||||
Expression<Func<Episode, object>> sort,
|
Expression<Func<Episode, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetEpisodes(where, new Sort<Episode>(sort), page);
|
) => GetEpisodes(where, new Sort<Episode>(sort), limit);
|
||||||
Task<ICollection<Track>> GetTracks([Optional] Expression<Func<Track, bool>> where,
|
Task<ICollection<Track>> GetTracks([Optional] Expression<Func<Track, bool>> where,
|
||||||
Expression<Func<Track, object>> sort,
|
Expression<Func<Track, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetTracks(where, new Sort<Track>(sort), page);
|
) => GetTracks(where, new Sort<Track>(sort), limit);
|
||||||
Task<ICollection<Studio>> GetStudios([Optional] Expression<Func<Studio, bool>> where,
|
Task<ICollection<Studio>> GetStudios([Optional] Expression<Func<Studio, bool>> where,
|
||||||
Expression<Func<Studio, object>> sort,
|
Expression<Func<Studio, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetStudios(where, new Sort<Studio>(sort), page);
|
) => GetStudios(where, new Sort<Studio>(sort), limit);
|
||||||
Task<ICollection<People>> GetPeople([Optional] Expression<Func<People, bool>> where,
|
Task<ICollection<People>> GetPeople([Optional] Expression<Func<People, bool>> where,
|
||||||
Expression<Func<People, object>> sort,
|
Expression<Func<People, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetPeople(where, new Sort<People>(sort), page);
|
) => GetPeople(where, new Sort<People>(sort), limit);
|
||||||
Task<ICollection<Genre>> GetGenres([Optional] Expression<Func<Genre, bool>> where,
|
Task<ICollection<Genre>> GetGenres([Optional] Expression<Func<Genre, bool>> where,
|
||||||
Expression<Func<Genre, object>> sort,
|
Expression<Func<Genre, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetGenres(where, new Sort<Genre>(sort), page);
|
) => GetGenres(where, new Sort<Genre>(sort), limit);
|
||||||
Task<ICollection<ProviderID>> GetProviders([Optional] Expression<Func<ProviderID, bool>> where,
|
Task<ICollection<ProviderID>> GetProviders([Optional] Expression<Func<ProviderID, bool>> where,
|
||||||
Expression<Func<ProviderID, object>> sort,
|
Expression<Func<ProviderID, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetProviders(where, new Sort<ProviderID>(sort), page);
|
) => GetProviders(where, new Sort<ProviderID>(sort), limit);
|
||||||
|
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
@ -19,6 +19,8 @@ namespace Kyoo.Controllers
|
|||||||
Count = count;
|
Count = count;
|
||||||
AfterID = afterID;
|
AfterID = afterID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static implicit operator Pagination(int limit) => new Pagination(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct Sort<T>
|
public readonly struct Sort<T>
|
||||||
@ -66,7 +68,7 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRepository<T> : IDisposable, IAsyncDisposable
|
public interface IRepository<T> : IDisposable, IAsyncDisposable where T : IRessource
|
||||||
{
|
{
|
||||||
Task<T> Get(int id);
|
Task<T> Get(int id);
|
||||||
Task<T> Get(string slug);
|
Task<T> Get(string slug);
|
||||||
@ -74,12 +76,12 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||||
Sort<T> sort = default,
|
Sort<T> sort = default,
|
||||||
Pagination page = default);
|
Pagination limit = default);
|
||||||
|
|
||||||
Task<ICollection<T>> GetAll([Optional] Expression<Func<T, bool>> where,
|
Task<ICollection<T>> GetAll([Optional] Expression<Func<T, bool>> where,
|
||||||
Expression<Func<T, object>> sort,
|
Expression<Func<T, object>> sort,
|
||||||
Pagination page = default
|
Pagination limit = default
|
||||||
) => GetAll(where, new Sort<T>(sort), page);
|
) => GetAll(where, new Sort<T>(sort), limit);
|
||||||
|
|
||||||
Task<T> Create([NotNull] T obj);
|
Task<T> Create([NotNull] T obj);
|
||||||
Task<T> CreateIfNotExists([NotNull] T obj);
|
Task<T> CreateIfNotExists([NotNull] T obj);
|
||||||
|
@ -143,9 +143,9 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public Task<ICollection<Show>> GetShows(Expression<Func<Show, bool>> where = null,
|
public Task<ICollection<Show>> GetShows(Expression<Func<Show, bool>> where = null,
|
||||||
Sort<Show> sort = default,
|
Sort<Show> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return _shows.GetAll(where, sort, page);
|
return _shows.GetAll(where, sort, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ICollection<Season>> GetSeasons(Expression<Func<Season, bool>> where = null,
|
public Task<ICollection<Season>> GetSeasons(Expression<Func<Season, bool>> where = null,
|
||||||
|
@ -5,7 +5,7 @@ using Kyoo.Models.Attributes;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Collection
|
public class Collection : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
public string Slug { get; set; }
|
public string Slug { get; set; }
|
||||||
|
@ -5,7 +5,7 @@ using Kyoo.Models.Attributes;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Episode : IOnMerge
|
public class Episode : IRessource, IOnMerge
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
[JsonIgnore] public int ShowID { get; set; }
|
[JsonIgnore] public int ShowID { get; set; }
|
||||||
|
@ -5,7 +5,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Genre
|
public class Genre : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
public string Slug { get; set; }
|
public string Slug { get; set; }
|
||||||
|
8
Kyoo.Common/Models/IRessource.cs
Normal file
8
Kyoo.Common/Models/IRessource.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public interface IRessource
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public string Slug { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Library
|
public class Library : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
public string Slug { get; set; }
|
public string Slug { get; set; }
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Page<T>
|
public class Page<T> where T : IRessource
|
||||||
{
|
{
|
||||||
public string This { get; set; }
|
public string This { get; set; }
|
||||||
public string First { get; set; }
|
public string First { get; set; }
|
||||||
@ -29,7 +28,6 @@ namespace Kyoo.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Page(ICollection<T> items,
|
public Page(ICollection<T> items,
|
||||||
Func<T, string> getID,
|
|
||||||
string url,
|
string url,
|
||||||
Dictionary<string, string> query,
|
Dictionary<string, string> query,
|
||||||
int limit)
|
int limit)
|
||||||
@ -39,7 +37,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
if (items.Count == limit)
|
if (items.Count == limit)
|
||||||
{
|
{
|
||||||
query["afterID"] = getID(items.Last());
|
query["afterID"] = items.Last().ID.ToString();
|
||||||
Next = url + query.ToQueryString();
|
Next = url + query.ToQueryString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class People
|
public class People : IRessource
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
public string Slug { get; set; }
|
public string Slug { get; set; }
|
||||||
|
@ -2,9 +2,10 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class ProviderID
|
public class ProviderID : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
|
public string Slug => Name;
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Logo { get; set; }
|
public string Logo { get; set; }
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Season
|
public class Season : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
[JsonIgnore] public int ShowID { get; set; }
|
[JsonIgnore] public int ShowID { get; set; }
|
||||||
|
@ -5,7 +5,7 @@ using Kyoo.Models.Attributes;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Show : IOnMerge
|
public class Show : IRessource, IOnMerge
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Studio
|
public class Studio : IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
public string Slug { get; set; }
|
public string Slug { get; set; }
|
||||||
|
@ -53,7 +53,7 @@ namespace Kyoo.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Track : Stream
|
public class Track : Stream, IRessource
|
||||||
{
|
{
|
||||||
[JsonIgnore] public int ID { get; set; }
|
[JsonIgnore] public int ID { get; set; }
|
||||||
[JsonIgnore] public int EpisodeID { get; set; }
|
[JsonIgnore] public int EpisodeID { get; set; }
|
||||||
|
@ -225,79 +225,7 @@ namespace Kyoo
|
|||||||
yield return ret;
|
yield return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetMemberName<T>(Expression<Func<T, object>> key)
|
|
||||||
{
|
|
||||||
if (key == null)
|
|
||||||
throw new ArgumentNullException(nameof(key));
|
|
||||||
|
|
||||||
if (!(key.Body is MemberExpression member))
|
|
||||||
throw new ArgumentException("Key should be a member of the object.");
|
|
||||||
|
|
||||||
return member.Member.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Expression StringCompatibleExpression(Func<Expression, Expression, BinaryExpression> operand,
|
|
||||||
Expression left,
|
|
||||||
Expression right)
|
|
||||||
{
|
|
||||||
if (left is MemberExpression member && ((PropertyInfo)member.Member).PropertyType == typeof(string))
|
|
||||||
{
|
|
||||||
MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right);
|
|
||||||
return operand(call, Expression.Constant(0));
|
|
||||||
}
|
|
||||||
return operand(left, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ((string key, string desired) in where)
|
|
||||||
{
|
|
||||||
string value = desired;
|
|
||||||
string operand = "eq";
|
|
||||||
if (desired.Contains(':'))
|
|
||||||
{
|
|
||||||
operand = desired.Substring(0, desired.IndexOf(':'));
|
|
||||||
value = desired.Substring(desired.IndexOf(':') + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
|
||||||
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);
|
|
||||||
|
|
||||||
Expression condition = operand switch
|
|
||||||
{
|
|
||||||
"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),
|
|
||||||
_ => throw new ArgumentException($"Invalid operand: {operand}")
|
|
||||||
};
|
|
||||||
|
|
||||||
if (expression != null)
|
|
||||||
expression = Expression.AndAlso(expression, condition);
|
|
||||||
else
|
|
||||||
expression = condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Expression.Lambda<Func<T, bool>>(expression!, param);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToQueryString(this Dictionary<string, string> query)
|
public static string ToQueryString(this Dictionary<string, string> query)
|
||||||
{
|
{
|
||||||
if (!query.Any())
|
if (!query.Any())
|
||||||
|
72
Kyoo.CommonAPI/ApiHelper.cs
Normal file
72
Kyoo.CommonAPI/ApiHelper.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Kyoo.CommonApi
|
||||||
|
{
|
||||||
|
public static class ApiHelper
|
||||||
|
{
|
||||||
|
public static Expression StringCompatibleExpression(Func<Expression, Expression, BinaryExpression> operand,
|
||||||
|
Expression left,
|
||||||
|
Expression right)
|
||||||
|
{
|
||||||
|
if (left is MemberExpression member && ((PropertyInfo)member.Member).PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
MethodCallExpression call = Expression.Call(typeof(string), "Compare", null, left, right);
|
||||||
|
return operand(call, Expression.Constant(0));
|
||||||
|
}
|
||||||
|
return operand(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ((string key, string desired) in where)
|
||||||
|
{
|
||||||
|
string value = desired;
|
||||||
|
string operand = "eq";
|
||||||
|
if (desired.Contains(':'))
|
||||||
|
{
|
||||||
|
operand = desired.Substring(0, desired.IndexOf(':'));
|
||||||
|
value = desired.Substring(desired.IndexOf(':') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||||
|
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);
|
||||||
|
|
||||||
|
Expression condition = operand switch
|
||||||
|
{
|
||||||
|
"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),
|
||||||
|
_ => throw new ArgumentException($"Invalid operand: {operand}")
|
||||||
|
};
|
||||||
|
|
||||||
|
if (expression != null)
|
||||||
|
expression = Expression.AndAlso(expression, condition);
|
||||||
|
else
|
||||||
|
expression = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expression.Lambda<Func<T, bool>>(expression!, param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
Kyoo.CommonAPI/CrudApi.cs
Normal file
153
Kyoo.CommonAPI/CrudApi.cs
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Kyoo.Models.Exceptions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace Kyoo.CommonApi
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
public class CrudApi<T> : ControllerBase where T : IRessource
|
||||||
|
{
|
||||||
|
private readonly IRepository<T> _repository;
|
||||||
|
private readonly string _baseURL;
|
||||||
|
|
||||||
|
public CrudApi(IRepository<T> repository, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_baseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[Authorize(Policy = "Read")]
|
||||||
|
[JsonDetailed]
|
||||||
|
public async Task<ActionResult<T>> Get(int id)
|
||||||
|
{
|
||||||
|
T ressource = await _repository.Get(id);
|
||||||
|
if (ressource == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return ressource;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{slug}")]
|
||||||
|
[Authorize(Policy = "Read")]
|
||||||
|
[JsonDetailed]
|
||||||
|
public async Task<ActionResult<T>> Get(string slug)
|
||||||
|
{
|
||||||
|
T ressource = await _repository.Get(slug);
|
||||||
|
if (ressource == null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return ressource;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "Read")]
|
||||||
|
public async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
|
||||||
|
[FromQuery] int limit,
|
||||||
|
[FromQuery] int afterID,
|
||||||
|
[FromQuery] Dictionary<string, string> where)
|
||||||
|
{
|
||||||
|
where.Remove("sortBy");
|
||||||
|
where.Remove("limit");
|
||||||
|
where.Remove("afterID");
|
||||||
|
if (limit == 0)
|
||||||
|
limit = 20;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ICollection<T> ressources = await _repository.GetAll(ApiHelper.ParseWhere<T>(where),
|
||||||
|
new Sort<T>(sortBy),
|
||||||
|
new Pagination(limit, afterID));
|
||||||
|
|
||||||
|
return new Page<T>(ressources,
|
||||||
|
_baseURL + Request.Path,
|
||||||
|
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
||||||
|
limit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
|
return BadRequest(new {Error = ex.Message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "Write")]
|
||||||
|
public async Task<ActionResult<T>> Create([FromBody] T ressource)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _repository.Create(ressource);
|
||||||
|
}
|
||||||
|
catch (DuplicatedItemException)
|
||||||
|
{
|
||||||
|
T existing = await _repository.Get(ressource.Slug);
|
||||||
|
return Conflict(existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[Authorize(Policy = "Write")]
|
||||||
|
public async Task<ActionResult<T>> Edit(int id, [FromQuery] bool resetOld, [FromBody] T ressource)
|
||||||
|
{
|
||||||
|
ressource.ID = id;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _repository.Edit(ressource, resetOld);
|
||||||
|
}
|
||||||
|
catch (ItemNotFound)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{slug}")]
|
||||||
|
[Authorize(Policy = "Write")]
|
||||||
|
public async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T ressource)
|
||||||
|
{
|
||||||
|
T old = await _repository.Get(slug);
|
||||||
|
if (old == null)
|
||||||
|
return NotFound();
|
||||||
|
ressource.ID = old.ID;
|
||||||
|
return await _repository.Edit(ressource, resetOld);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize(Policy = "Write")]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _repository.Delete(id);
|
||||||
|
}
|
||||||
|
catch (ItemNotFound)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{slug}")]
|
||||||
|
[Authorize(Policy = "Write")]
|
||||||
|
public async Task<IActionResult> Delete(string slug)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _repository.Delete(slug);
|
||||||
|
}
|
||||||
|
catch (ItemNotFound)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Kyoo.CommonAPI/Kyoo.CommonAPI.csproj
Normal file
22
Kyoo.CommonAPI/Kyoo.CommonAPI.csproj
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<AssemblyName>Kyoo.CommonApi</AssemblyName>
|
||||||
|
<RootNamespace>Kyoo.CommonApi</RootNamespace>
|
||||||
|
<PackageId>Kyoo.CommonApi</PackageId>
|
||||||
|
<Authors>AnonymusRaccoon</Authors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
||||||
|
<PackageReference Include="Npgsql" Version="4.1.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Kyoo.Common\Kyoo.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
156
Kyoo.CommonAPI/LocalRepository.cs
Normal file
156
Kyoo.CommonAPI/LocalRepository.cs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.CommonApi;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Kyoo.Models.Exceptions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Npgsql;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public abstract class LocalRepository<T> : IRepository<T> where T : class, IRessource
|
||||||
|
{
|
||||||
|
private readonly DbContext _database;
|
||||||
|
|
||||||
|
protected abstract Expression<Func<T, object>> DefaultSort { get; }
|
||||||
|
|
||||||
|
|
||||||
|
protected LocalRepository(DbContext database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Dispose()
|
||||||
|
{
|
||||||
|
_database.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
return _database.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task<T> Get(int id)
|
||||||
|
{
|
||||||
|
return _database.Set<T>().FirstOrDefaultAsync(x => x.ID == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Task<T> Get(string slug)
|
||||||
|
{
|
||||||
|
return _database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<ICollection<T>> Search(string query);
|
||||||
|
|
||||||
|
public virtual async Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||||
|
Sort<T> sort = default,
|
||||||
|
Pagination limit = default)
|
||||||
|
{
|
||||||
|
IQueryable<T> query = _database.Set<T>();
|
||||||
|
|
||||||
|
if (where != null)
|
||||||
|
query = query.Where(where);
|
||||||
|
|
||||||
|
Expression<Func<T, object>> sortKey = sort.Key ?? DefaultSort;
|
||||||
|
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
||||||
|
|
||||||
|
if (limit.AfterID != 0)
|
||||||
|
{
|
||||||
|
T after = await Get(limit.AfterID);
|
||||||
|
object afterObj = sortKey.Compile()(after);
|
||||||
|
query = query.Where(Expression.Lambda<Func<T, bool>>(
|
||||||
|
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)),
|
||||||
|
(ParameterExpression)((MemberExpression)sortKey.Body).Expression
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (limit.Count > 0)
|
||||||
|
query = query.Take(limit.Count);
|
||||||
|
|
||||||
|
return await query.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<T> Create(T obj);
|
||||||
|
|
||||||
|
public virtual async Task<T> CreateIfNotExists(T obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
|
||||||
|
T old = await Get(obj.Slug);
|
||||||
|
if (old != null)
|
||||||
|
return old;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await Create(obj);
|
||||||
|
}
|
||||||
|
catch (DuplicatedItemException)
|
||||||
|
{
|
||||||
|
old = await Get(obj.Slug);
|
||||||
|
if (old == null)
|
||||||
|
throw new SystemException("Unknown database state.");
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<T> Edit(T edited, bool resetOld)
|
||||||
|
{
|
||||||
|
if (edited == null)
|
||||||
|
throw new ArgumentNullException(nameof(edited));
|
||||||
|
|
||||||
|
T old = await Get(edited.Slug);
|
||||||
|
|
||||||
|
if (old == null)
|
||||||
|
throw new ItemNotFound($"No ressource found with the slug {edited.Slug}.");
|
||||||
|
|
||||||
|
if (resetOld)
|
||||||
|
Utility.Nullify(old);
|
||||||
|
Utility.Merge(old, edited);
|
||||||
|
await Validate(old);
|
||||||
|
await _database.SaveChangesAsync();
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task Validate(T ressource);
|
||||||
|
|
||||||
|
public virtual async Task Delete(int id)
|
||||||
|
{
|
||||||
|
T ressource = await Get(id);
|
||||||
|
await Delete(ressource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task Delete(string slug)
|
||||||
|
{
|
||||||
|
T ressource = await Get(slug);
|
||||||
|
await Delete(ressource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task Delete(T obj);
|
||||||
|
|
||||||
|
public virtual async Task DeleteRange(IEnumerable<T> objs)
|
||||||
|
{
|
||||||
|
foreach (T obj in objs)
|
||||||
|
await Delete(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task DeleteRange(IEnumerable<int> ids)
|
||||||
|
{
|
||||||
|
foreach (int id in ids)
|
||||||
|
await Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task DeleteRange(IEnumerable<string> slugs)
|
||||||
|
{
|
||||||
|
foreach (string slug in slugs)
|
||||||
|
await Delete(slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDuplicateException(DbUpdateException ex)
|
||||||
|
{
|
||||||
|
return ex.InnerException is PostgresException inner
|
||||||
|
&& inner.SqlState == PostgresErrorCodes.UniqueViolation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
Kyoo.sln
6
Kyoo.sln
@ -3,6 +3,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo", "Kyoo\Kyoo.csproj",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\Kyoo.Common.csproj", "{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\Kyoo.Common.csproj", "{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -17,5 +19,9 @@ Global
|
|||||||
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.Build.0 = Release|Any CPU
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -9,37 +9,17 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class CollectionRepository : ICollectionRepository
|
public class CollectionRepository : LocalRepository<Collection>, ICollectionRepository
|
||||||
{
|
{
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
|
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
|
||||||
|
|
||||||
|
public CollectionRepository(DatabaseContext database) : base(database)
|
||||||
public CollectionRepository(DatabaseContext database)
|
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public override async Task<ICollection<Collection>> Search(string query)
|
||||||
{
|
|
||||||
_database.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
return _database.DisposeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Collection> Get(int id)
|
|
||||||
{
|
|
||||||
return _database.Collections.FirstOrDefaultAsync(x => x.ID == id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Collection> Get(string slug)
|
|
||||||
{
|
|
||||||
return _database.Collections.FirstOrDefaultAsync(x => x.Slug == slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ICollection<Collection>> Search(string query)
|
|
||||||
{
|
{
|
||||||
return await _database.Collections
|
return await _database.Collections
|
||||||
.Where(x => EF.Functions.Like(x.Name, $"%{query}%"))
|
.Where(x => EF.Functions.Like(x.Name, $"%{query}%"))
|
||||||
@ -47,32 +27,7 @@ namespace Kyoo.Controllers
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Collection>> GetAll(Expression<Func<Collection, bool>> where = null,
|
public override async Task<Collection> Create(Collection obj)
|
||||||
Sort<Collection> sort = default,
|
|
||||||
Pagination page = default)
|
|
||||||
{
|
|
||||||
IQueryable<Collection> query = _database.Collections;
|
|
||||||
|
|
||||||
if (where != null)
|
|
||||||
query = query.Where(where);
|
|
||||||
|
|
||||||
Expression<Func<Collection, object>> sortKey = sort.Key ?? (x => x.Name);
|
|
||||||
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
|
||||||
|
|
||||||
if (page.AfterID != 0)
|
|
||||||
{
|
|
||||||
Collection after = await Get(page.AfterID);
|
|
||||||
object afterObj = sortKey.Compile()(after);
|
|
||||||
query = query.Where(Expression.Lambda<Func<Collection, bool>>(
|
|
||||||
Expression.GreaterThan(sortKey, Expression.Constant(afterObj))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
query = query.Take(page.Count <= 0 ? 20 : page.Count);
|
|
||||||
|
|
||||||
return await query.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Collection> Create(Collection obj)
|
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
@ -86,66 +41,20 @@ namespace Kyoo.Controllers
|
|||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
_database.DiscardChanges();
|
_database.DiscardChanges();
|
||||||
if (Helper.IsDuplicateException(ex))
|
if (IsDuplicateException(ex))
|
||||||
throw new DuplicatedItemException($"Trying to insert a duplicated collection (slug {obj.Slug} already exists).");
|
throw new DuplicatedItemException($"Trying to insert a duplicated collection (slug {obj.Slug} already exists).");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Collection> CreateIfNotExists(Collection obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
|
|
||||||
Collection old = await Get(obj.Slug);
|
protected override Task Validate(Collection ressource)
|
||||||
if (old != null)
|
{
|
||||||
return old;
|
return Task.CompletedTask;
|
||||||
try
|
|
||||||
{
|
|
||||||
return await Create(obj);
|
|
||||||
}
|
|
||||||
catch (DuplicatedItemException)
|
|
||||||
{
|
|
||||||
old = await Get(obj.Slug);
|
|
||||||
if (old == null)
|
|
||||||
throw new SystemException("Unknown database state.");
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Collection> Edit(Collection edited, bool resetOld)
|
public override async Task Delete(Collection obj)
|
||||||
{
|
|
||||||
if (edited == null)
|
|
||||||
throw new ArgumentNullException(nameof(edited));
|
|
||||||
|
|
||||||
Collection old = await Get(edited.Slug);
|
|
||||||
|
|
||||||
if (old == null)
|
|
||||||
throw new ItemNotFound($"No collection found with the slug {edited.Slug}.");
|
|
||||||
|
|
||||||
if (resetOld)
|
|
||||||
Utility.Nullify(old);
|
|
||||||
Utility.Merge(old, edited);
|
|
||||||
|
|
||||||
await _database.SaveChangesAsync();
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(int id)
|
|
||||||
{
|
|
||||||
Collection obj = await Get(id);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(string slug)
|
|
||||||
{
|
|
||||||
Collection obj = await Get(slug);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(Collection obj)
|
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
@ -159,23 +68,5 @@ namespace Kyoo.Controllers
|
|||||||
_database.Entry(link).State = EntityState.Deleted;
|
_database.Entry(link).State = EntityState.Deleted;
|
||||||
await _database.SaveChangesAsync();
|
await _database.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<Collection> objs)
|
|
||||||
{
|
|
||||||
foreach (Collection obj in objs)
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<int> ids)
|
|
||||||
{
|
|
||||||
foreach (int id in ids)
|
|
||||||
await Delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<string> slugs)
|
|
||||||
{
|
|
||||||
foreach (string slug in slugs)
|
|
||||||
await Delete(slug);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,35 +10,33 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class EpisodeRepository : IEpisodeRepository
|
public class EpisodeRepository : LocalRepository<Episode>, IEpisodeRepository
|
||||||
{
|
{
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
private readonly IProviderRepository _providers;
|
private readonly IProviderRepository _providers;
|
||||||
// private readonly ITrackRepository _tracks;
|
protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber;
|
||||||
|
|
||||||
|
|
||||||
public EpisodeRepository(DatabaseContext database, IProviderRepository providers)
|
public EpisodeRepository(DatabaseContext database, IProviderRepository providers) : base(database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
_providers = providers;
|
_providers = providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
_database.Dispose();
|
_database.Dispose();
|
||||||
|
_providers.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
public override async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
return _database.DisposeAsync();
|
await _database.DisposeAsync();
|
||||||
}
|
await _providers.DisposeAsync();
|
||||||
|
|
||||||
public Task<Episode> Get(int id)
|
|
||||||
{
|
|
||||||
return _database.Episodes.FirstOrDefaultAsync(x => x.ID == id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Episode> Get(string slug)
|
public override Task<Episode> Get(string slug)
|
||||||
{
|
{
|
||||||
Match match = Regex.Match(slug, @"(<show>.*)-s(<season>\d*)-e(<episode>\d*)");
|
Match match = Regex.Match(slug, @"(<show>.*)-s(<season>\d*)-e(<episode>\d*)");
|
||||||
|
|
||||||
@ -56,7 +54,7 @@ namespace Kyoo.Controllers
|
|||||||
&& x.EpisodeNumber == episodeNumber);
|
&& x.EpisodeNumber == episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Episode>> Search(string query)
|
public override async Task<ICollection<Episode>> Search(string query)
|
||||||
{
|
{
|
||||||
return await _database.Episodes
|
return await _database.Episodes
|
||||||
.Where(x => EF.Functions.Like(x.Title, $"%{query}%"))
|
.Where(x => EF.Functions.Like(x.Title, $"%{query}%"))
|
||||||
@ -64,14 +62,7 @@ namespace Kyoo.Controllers
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Episode>> GetAll(Expression<Func<Episode, bool>> where = null,
|
public override async Task<Episode> Create(Episode obj)
|
||||||
Sort<Episode> sort = default,
|
|
||||||
Pagination page = default)
|
|
||||||
{
|
|
||||||
return await _database.Episodes.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Episode> Create(Episode obj)
|
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
@ -82,7 +73,6 @@ namespace Kyoo.Controllers
|
|||||||
foreach (MetadataID entry in obj.ExternalIDs)
|
foreach (MetadataID entry in obj.ExternalIDs)
|
||||||
_database.Entry(entry).State = EntityState.Added;
|
_database.Entry(entry).State = EntityState.Added;
|
||||||
|
|
||||||
// Since Episodes & Tracks are on the same DB, using a single commit is quicker.
|
|
||||||
if (obj.Tracks != null)
|
if (obj.Tracks != null)
|
||||||
foreach (Track entry in obj.Tracks)
|
foreach (Track entry in obj.Tracks)
|
||||||
_database.Entry(entry).State = EntityState.Added;
|
_database.Entry(entry).State = EntityState.Added;
|
||||||
@ -95,64 +85,15 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
_database.DiscardChanges();
|
_database.DiscardChanges();
|
||||||
|
|
||||||
if (Helper.IsDuplicateException(ex))
|
if (IsDuplicateException(ex))
|
||||||
throw new DuplicatedItemException($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
|
throw new DuplicatedItemException($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since Episodes & Tracks are on the same DB, using a single commit is quicker.
|
|
||||||
/*if (obj.Tracks != null)
|
|
||||||
* foreach (Track track in obj.Tracks)
|
|
||||||
* {
|
|
||||||
* track.EpisodeID = obj.ID;
|
|
||||||
* await _tracks.Create(track);
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Episode> CreateIfNotExists(Episode obj)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
|
|
||||||
Episode old = await Get(obj.Slug);
|
protected override async Task Validate(Episode obj)
|
||||||
if (old != null)
|
|
||||||
return old;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await Create(obj);
|
|
||||||
}
|
|
||||||
catch (DuplicatedItemException)
|
|
||||||
{
|
|
||||||
old = await Get(obj.Slug);
|
|
||||||
if (old == null)
|
|
||||||
throw new SystemException("Unknown database state.");
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Episode> Edit(Episode edited, bool resetOld)
|
|
||||||
{
|
|
||||||
if (edited == null)
|
|
||||||
throw new ArgumentNullException(nameof(edited));
|
|
||||||
|
|
||||||
Episode old = await Get(edited.Slug);
|
|
||||||
|
|
||||||
if (old == null)
|
|
||||||
throw new ItemNotFound($"No episode found with the slug {edited.Slug}.");
|
|
||||||
|
|
||||||
if (resetOld)
|
|
||||||
Utility.Nullify(old);
|
|
||||||
Utility.Merge(old, edited);
|
|
||||||
|
|
||||||
await Validate(old);
|
|
||||||
await _database.SaveChangesAsync();
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Validate(Episode obj)
|
|
||||||
{
|
{
|
||||||
if (obj.ShowID <= 0)
|
if (obj.ShowID <= 0)
|
||||||
throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {obj.ShowID}).");
|
throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {obj.ShowID}).");
|
||||||
@ -181,25 +122,13 @@ namespace Kyoo.Controllers
|
|||||||
return await _database.Episodes.Where(x => x.SeasonID == seasonID).ToListAsync();
|
return await _database.Episodes.Where(x => x.SeasonID == seasonID).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete(int id)
|
|
||||||
{
|
|
||||||
Episode obj = await Get(id);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(string slug)
|
|
||||||
{
|
|
||||||
Episode obj = await Get(slug);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(string showSlug, int seasonNumber, int episodeNumber)
|
public async Task Delete(string showSlug, int seasonNumber, int episodeNumber)
|
||||||
{
|
{
|
||||||
Episode obj = await Get(showSlug, seasonNumber, episodeNumber);
|
Episode obj = await Get(showSlug, seasonNumber, episodeNumber);
|
||||||
await Delete(obj);
|
await Delete(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete(Episode obj)
|
public override async Task Delete(Episode obj)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
@ -211,23 +140,5 @@ namespace Kyoo.Controllers
|
|||||||
// Since Tracks & Episodes are on the same database and handled by dotnet-ef, we can't use the repository to delete them.
|
// Since Tracks & Episodes are on the same database and handled by dotnet-ef, we can't use the repository to delete them.
|
||||||
await _database.SaveChangesAsync();
|
await _database.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<Episode> objs)
|
|
||||||
{
|
|
||||||
foreach (Episode obj in objs)
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<int> ids)
|
|
||||||
{
|
|
||||||
foreach (int id in ids)
|
|
||||||
await Delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<string> slugs)
|
|
||||||
{
|
|
||||||
foreach (string slug in slugs)
|
|
||||||
await Delete(slug);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,35 +9,17 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class GenreRepository : IGenreRepository
|
public class GenreRepository : LocalRepository<Genre>, IGenreRepository
|
||||||
{
|
{
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
|
protected override Expression<Func<Genre, object>> DefaultSort => x => x.Slug;
|
||||||
|
|
||||||
|
|
||||||
public GenreRepository(DatabaseContext database)
|
public GenreRepository(DatabaseContext database) : base(database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_database.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
return _database.DisposeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Genre> Get(int id)
|
|
||||||
{
|
|
||||||
return await _database.Genres.FirstOrDefaultAsync(x => x.ID == id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Genre> Get(string slug)
|
|
||||||
{
|
|
||||||
return await _database.Genres.FirstOrDefaultAsync(x => x.Slug == slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ICollection<Genre>> Search(string query)
|
public async Task<ICollection<Genre>> Search(string query)
|
||||||
{
|
{
|
||||||
@ -47,14 +29,7 @@ namespace Kyoo.Controllers
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICollection<Genre>> GetAll(Expression<Func<Genre, bool>> where = null,
|
public override async Task<Genre> Create(Genre obj)
|
||||||
Sort<Genre> sort = default,
|
|
||||||
Pagination page = default)
|
|
||||||
{
|
|
||||||
return await _database.Genres.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Genre> Create(Genre obj)
|
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
@ -69,7 +44,7 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
_database.DiscardChanges();
|
_database.DiscardChanges();
|
||||||
|
|
||||||
if (Helper.IsDuplicateException(ex))
|
if (IsDuplicateException(ex))
|
||||||
throw new DuplicatedItemException($"Trying to insert a duplicated genre (slug {obj.Slug} already exists).");
|
throw new DuplicatedItemException($"Trying to insert a duplicated genre (slug {obj.Slug} already exists).");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@ -77,54 +52,9 @@ namespace Kyoo.Controllers
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Genre> CreateIfNotExists(Genre obj)
|
protected override Task Validate(Genre ressource)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
return Task.CompletedTask;
|
||||||
throw new ArgumentNullException(nameof(obj));
|
|
||||||
|
|
||||||
Genre old = await Get(obj.Slug);
|
|
||||||
if (old != null)
|
|
||||||
return old;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await Create(obj);
|
|
||||||
}
|
|
||||||
catch (DuplicatedItemException)
|
|
||||||
{
|
|
||||||
old = await Get(obj.Slug);
|
|
||||||
if (old == null)
|
|
||||||
throw new SystemException("Unknown database state.");
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Genre> Edit(Genre edited, bool resetOld)
|
|
||||||
{
|
|
||||||
if (edited == null)
|
|
||||||
throw new ArgumentNullException(nameof(edited));
|
|
||||||
|
|
||||||
Genre old = await Get(edited.Slug);
|
|
||||||
|
|
||||||
if (old == null)
|
|
||||||
throw new ItemNotFound($"No genre found with the slug {edited.Slug}.");
|
|
||||||
|
|
||||||
if (resetOld)
|
|
||||||
Utility.Nullify(old);
|
|
||||||
Utility.Merge(old, edited);
|
|
||||||
await _database.SaveChangesAsync();
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(int id)
|
|
||||||
{
|
|
||||||
Genre obj = await Get(id);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(string slug)
|
|
||||||
{
|
|
||||||
Genre obj = await Get(slug);
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete(Genre obj)
|
public async Task Delete(Genre obj)
|
||||||
@ -138,23 +68,5 @@ namespace Kyoo.Controllers
|
|||||||
_database.Entry(link).State = EntityState.Deleted;
|
_database.Entry(link).State = EntityState.Deleted;
|
||||||
await _database.SaveChangesAsync();
|
await _database.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<Genre> objs)
|
|
||||||
{
|
|
||||||
foreach (Genre obj in objs)
|
|
||||||
await Delete(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<int> ids)
|
|
||||||
{
|
|
||||||
foreach (int id in ids)
|
|
||||||
await Delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteRange(IEnumerable<string> slugs)
|
|
||||||
{
|
|
||||||
foreach (string slug in slugs)
|
|
||||||
await Delete(slug);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Npgsql;
|
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
|
||||||
{
|
|
||||||
public static class Helper
|
|
||||||
{
|
|
||||||
public static bool IsDuplicateException(DbUpdateException ex)
|
|
||||||
{
|
|
||||||
return ex.InnerException is PostgresException inner
|
|
||||||
&& inner.SqlState == PostgresErrorCodes.UniqueViolation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,7 +51,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<Library>> GetAll(Expression<Func<Library, bool>> where = null,
|
public async Task<ICollection<Library>> GetAll(Expression<Func<Library, bool>> where = null,
|
||||||
Sort<Library> sort = default,
|
Sort<Library> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Libraries.ToListAsync();
|
return await _database.Libraries.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<People>> GetAll(Expression<Func<People, bool>> where = null,
|
public async Task<ICollection<People>> GetAll(Expression<Func<People, bool>> where = null,
|
||||||
Sort<People> sort = default,
|
Sort<People> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Peoples.ToListAsync();
|
return await _database.Peoples.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<ProviderID>> GetAll(Expression<Func<ProviderID, bool>> where = null,
|
public async Task<ICollection<ProviderID>> GetAll(Expression<Func<ProviderID, bool>> where = null,
|
||||||
Sort<ProviderID> sort = default,
|
Sort<ProviderID> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Providers.ToListAsync();
|
return await _database.Providers.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<Season>> GetAll(Expression<Func<Season, bool>> where = null,
|
public async Task<ICollection<Season>> GetAll(Expression<Func<Season, bool>> where = null,
|
||||||
Sort<Season> sort = default,
|
Sort<Season> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Seasons.ToListAsync();
|
return await _database.Seasons.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.CommonApi;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -73,7 +74,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<Show>> GetAll(Expression<Func<Show, bool>> where = null,
|
public async Task<ICollection<Show>> GetAll(Expression<Func<Show, bool>> where = null,
|
||||||
Sort<Show> sort = default,
|
Sort<Show> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
IQueryable<Show> query = _database.Shows;
|
IQueryable<Show> query = _database.Shows;
|
||||||
|
|
||||||
@ -83,17 +84,17 @@ namespace Kyoo.Controllers
|
|||||||
Expression<Func<Show, object>> sortKey = sort.Key ?? (x => x.Title);
|
Expression<Func<Show, object>> sortKey = sort.Key ?? (x => x.Title);
|
||||||
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey);
|
||||||
|
|
||||||
if (page.AfterID != 0)
|
if (limit.AfterID != 0)
|
||||||
{
|
{
|
||||||
Show after = await Get(page.AfterID);
|
Show after = await Get(limit.AfterID);
|
||||||
object afterObj = sortKey.Compile()(after);
|
object afterObj = sortKey.Compile()(after);
|
||||||
query = query.Where(Expression.Lambda<Func<Show, bool>>(
|
query = query.Where(Expression.Lambda<Func<Show, bool>>(
|
||||||
Utility.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)),
|
ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)),
|
||||||
(ParameterExpression)((MemberExpression)sortKey.Body).Expression
|
(ParameterExpression)((MemberExpression)sortKey.Body).Expression
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if (page.Count > 0)
|
if (limit.Count > 0)
|
||||||
query = query.Take(page.Count);
|
query = query.Take(limit.Count);
|
||||||
|
|
||||||
return await query.ToListAsync();
|
return await query.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<Studio>> GetAll(Expression<Func<Studio, bool>> where = null,
|
public async Task<ICollection<Studio>> GetAll(Expression<Func<Studio, bool>> where = null,
|
||||||
Sort<Studio> sort = default,
|
Sort<Studio> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Studios.ToListAsync();
|
return await _database.Studios.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<ICollection<Track>> GetAll(Expression<Func<Track, bool>> where = null,
|
public async Task<ICollection<Track>> GetAll(Expression<Func<Track, bool>> where = null,
|
||||||
Sort<Track> sort = default,
|
Sort<Track> sort = default,
|
||||||
Pagination page = default)
|
Pagination limit = default)
|
||||||
{
|
{
|
||||||
return await _database.Tracks.ToListAsync();
|
return await _database.Tracks.ToListAsync();
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
<ProjectReference Include="..\Kyoo.CommonAPI\Kyoo.CommonAPI.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -224,7 +224,8 @@ namespace Kyoo.Controllers
|
|||||||
bool isMovie,
|
bool isMovie,
|
||||||
Library library)
|
Library library)
|
||||||
{
|
{
|
||||||
Show show = await libraryManager.GetShowByPath(showPath);
|
Show show = (await libraryManager.GetShows(x => x.Path == showPath, limit: 1))
|
||||||
|
.FirstOrDefault();
|
||||||
if (show != null)
|
if (show != null)
|
||||||
return show;
|
return show;
|
||||||
show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
|
show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
|
||||||
|
@ -16,167 +16,6 @@ namespace Kyoo.API
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class ShowsAPI : ControllerBase
|
public class ShowsAPI : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IShowRepository _shows;
|
|
||||||
private readonly IProviderManager _providerManager;
|
|
||||||
private readonly IThumbnailsManager _thumbnailsManager;
|
|
||||||
private readonly ITaskManager _taskManager;
|
|
||||||
private readonly string _baseURL;
|
|
||||||
|
|
||||||
public ShowsAPI(IShowRepository shows,
|
|
||||||
IProviderManager providerManager,
|
|
||||||
IThumbnailsManager thumbnailsManager,
|
|
||||||
ITaskManager taskManager,
|
|
||||||
IConfiguration configuration)
|
|
||||||
{
|
|
||||||
_shows = shows;
|
|
||||||
_providerManager = providerManager;
|
|
||||||
_thumbnailsManager = thumbnailsManager;
|
|
||||||
_taskManager = taskManager;
|
|
||||||
_baseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Authorize(Policy="Read")]
|
|
||||||
public async Task<ActionResult<Page<Show>>> GetShows([FromQuery] string sortBy,
|
|
||||||
[FromQuery] int limit,
|
|
||||||
[FromQuery] int afterID,
|
|
||||||
[FromQuery] Dictionary<string, string> where)
|
|
||||||
{
|
|
||||||
where.Remove("sortBy");
|
|
||||||
where.Remove("limit");
|
|
||||||
where.Remove("afterID");
|
|
||||||
if (limit == 0)
|
|
||||||
limit = 20;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ICollection<Show> shows = await _shows.GetAll(Utility.ParseWhere<Show>(where),
|
|
||||||
new Sort<Show>(sortBy),
|
|
||||||
new Pagination(limit, afterID));
|
|
||||||
|
|
||||||
return new Page<Show>(shows,
|
|
||||||
x => $"{x.ID}",
|
|
||||||
_baseURL + Request.Path,
|
|
||||||
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
|
|
||||||
limit);
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
return BadRequest(new { Error = ex.Message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
|
||||||
[Authorize(Policy="Read")]
|
|
||||||
[JsonDetailed]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(int id)
|
|
||||||
{
|
|
||||||
Show show = await _shows.Get(id);
|
|
||||||
if (show == null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
return show;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{slug}")]
|
|
||||||
[Authorize(Policy="Read")]
|
|
||||||
[JsonDetailed]
|
|
||||||
public async Task<ActionResult<Show>> GetShow(string slug)
|
|
||||||
{
|
|
||||||
Show show = await _shows.Get(slug);
|
|
||||||
if (show == null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
return show;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[Authorize(Policy="Write")]
|
|
||||||
public async Task<ActionResult<Show>> CreateShow([FromBody] Show show)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _shows.Create(show);
|
|
||||||
}
|
|
||||||
catch (DuplicatedItemException)
|
|
||||||
{
|
|
||||||
Show existing = await _shows.Get(show.Slug);
|
|
||||||
return Conflict(existing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("{slug}")]
|
|
||||||
[Authorize(Policy="Write")]
|
|
||||||
public async Task<ActionResult<Show>> EditShow(string slug, [FromQuery] bool resetOld, [FromBody] Show show)
|
|
||||||
{
|
|
||||||
Show old = await _shows.Get(slug);
|
|
||||||
if (old == null)
|
|
||||||
return NotFound();
|
|
||||||
show.ID = old.ID;
|
|
||||||
show.Path = old.Path;
|
|
||||||
return await _shows.Edit(show, resetOld);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{slug}")]
|
|
||||||
// [Authorize(Policy="Write")]
|
|
||||||
public async Task<IActionResult> DeleteShow(string slug)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _shows.Delete(slug);
|
|
||||||
}
|
|
||||||
catch (ItemNotFound)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
|
||||||
// [Authorize(Policy="Write")]
|
|
||||||
public async Task<IActionResult> DeleteShow(int id)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _shows.Delete(id);
|
|
||||||
}
|
|
||||||
catch (ItemNotFound)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("re-identify/{slug}")]
|
|
||||||
[Authorize(Policy = "Write")]
|
|
||||||
public IActionResult ReIdentityShow(string slug, [FromBody] IEnumerable<MetadataID> externalIDs)
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
return BadRequest(externalIDs);
|
|
||||||
Show show = _database.Shows.Include(x => x.ExternalIDs).FirstOrDefault(x => x.Slug == slug);
|
|
||||||
if (show == null)
|
|
||||||
return NotFound();
|
|
||||||
_database.SaveChanges();
|
|
||||||
_taskManager.StartTask("re-scan", $"show/{slug}");
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("identify/{name}")]
|
|
||||||
[Authorize(Policy = "Read")]
|
|
||||||
public async Task<IEnumerable<Show>> IdentityShow(string name, [FromQuery] bool isMovie)
|
|
||||||
{
|
|
||||||
return await _providerManager.SearchShows(name, isMovie, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("download-images/{slug}")]
|
|
||||||
[Authorize(Policy = "Write")]
|
|
||||||
public async Task<IActionResult> DownloadImages(string slug)
|
|
||||||
{
|
|
||||||
Show show = await _libraryManager.GetShow(slug);
|
|
||||||
if (show == null)
|
|
||||||
return NotFound();
|
|
||||||
await _thumbnailsManager.Validate(show, true);
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user