diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 6da3a7b7..232c1cdb 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -39,75 +39,75 @@ namespace Kyoo.Controllers // Get all Task> GetLibraries(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetCollections(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetShows(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetSeasons(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetEpisodes(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetTracks(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetStudios(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetPeople(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetGenres(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetProviders(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetLibraries([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetLibraries(where, new Sort(sort), page); + Pagination limit = default + ) => GetLibraries(where, new Sort(sort), limit); Task> GetCollections([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetCollections(where, new Sort(sort), page); + Pagination limit = default + ) => GetCollections(where, new Sort(sort), limit); Task> GetShows([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetShows(where, new Sort(sort), page); + Pagination limit = default + ) => GetShows(where, new Sort(sort), limit); Task> GetSeasons([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetSeasons(where, new Sort(sort), page); + Pagination limit = default + ) => GetSeasons(where, new Sort(sort), limit); Task> GetEpisodes([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetEpisodes(where, new Sort(sort), page); + Pagination limit = default + ) => GetEpisodes(where, new Sort(sort), limit); Task> GetTracks([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetTracks(where, new Sort(sort), page); + Pagination limit = default + ) => GetTracks(where, new Sort(sort), limit); Task> GetStudios([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetStudios(where, new Sort(sort), page); + Pagination limit = default + ) => GetStudios(where, new Sort(sort), limit); Task> GetPeople([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetPeople(where, new Sort(sort), page); + Pagination limit = default + ) => GetPeople(where, new Sort(sort), limit); Task> GetGenres([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetGenres(where, new Sort(sort), page); + Pagination limit = default + ) => GetGenres(where, new Sort(sort), limit); Task> GetProviders([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetProviders(where, new Sort(sort), page); + Pagination limit = default + ) => GetProviders(where, new Sort(sort), limit); // Search diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 69a09df3..54bcb678 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -19,6 +19,8 @@ namespace Kyoo.Controllers Count = count; AfterID = afterID; } + + public static implicit operator Pagination(int limit) => new Pagination(limit); } public readonly struct Sort @@ -66,7 +68,7 @@ namespace Kyoo.Controllers } } - public interface IRepository : IDisposable, IAsyncDisposable + public interface IRepository : IDisposable, IAsyncDisposable where T : IRessource { Task Get(int id); Task Get(string slug); @@ -74,12 +76,12 @@ namespace Kyoo.Controllers Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default); + Pagination limit = default); Task> GetAll([Optional] Expression> where, Expression> sort, - Pagination page = default - ) => GetAll(where, new Sort(sort), page); + Pagination limit = default + ) => GetAll(where, new Sort(sort), limit); Task Create([NotNull] T obj); Task CreateIfNotExists([NotNull] T obj); diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 2ffbcaca..cd3f080d 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -143,9 +143,9 @@ namespace Kyoo.Controllers public Task> GetShows(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { - return _shows.GetAll(where, sort, page); + return _shows.GetAll(where, sort, limit); } public Task> GetSeasons(Expression> where = null, diff --git a/Kyoo.Common/Models/Collection.cs b/Kyoo.Common/Models/Collection.cs index 5336c52c..a32478f4 100644 --- a/Kyoo.Common/Models/Collection.cs +++ b/Kyoo.Common/Models/Collection.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Collection + public class Collection : IRessource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Episode.cs b/Kyoo.Common/Models/Episode.cs index a90dc5d9..c9725fe3 100644 --- a/Kyoo.Common/Models/Episode.cs +++ b/Kyoo.Common/Models/Episode.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Episode : IOnMerge + public class Episode : IRessource, IOnMerge { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int ShowID { get; set; } diff --git a/Kyoo.Common/Models/Genre.cs b/Kyoo.Common/Models/Genre.cs index 1f964eff..fe4a2533 100644 --- a/Kyoo.Common/Models/Genre.cs +++ b/Kyoo.Common/Models/Genre.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Genre + public class Genre : IRessource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/IRessource.cs b/Kyoo.Common/Models/IRessource.cs new file mode 100644 index 00000000..6f86a4af --- /dev/null +++ b/Kyoo.Common/Models/IRessource.cs @@ -0,0 +1,8 @@ +namespace Kyoo.Models +{ + public interface IRessource + { + public int ID { get; set; } + public string Slug { get; } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Library.cs b/Kyoo.Common/Models/Library.cs index 84441297..3b192df8 100644 --- a/Kyoo.Common/Models/Library.cs +++ b/Kyoo.Common/Models/Library.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Library + public class Library : IRessource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Page.cs b/Kyoo.Common/Models/Page.cs index 3f751613..5d4505e7 100644 --- a/Kyoo.Common/Models/Page.cs +++ b/Kyoo.Common/Models/Page.cs @@ -1,10 +1,9 @@ -using System; using System.Collections.Generic; using System.Linq; namespace Kyoo.Models { - public class Page + public class Page where T : IRessource { public string This { get; set; } public string First { get; set; } @@ -29,7 +28,6 @@ namespace Kyoo.Models } public Page(ICollection items, - Func getID, string url, Dictionary query, int limit) @@ -39,7 +37,7 @@ namespace Kyoo.Models if (items.Count == limit) { - query["afterID"] = getID(items.Last()); + query["afterID"] = items.Last().ID.ToString(); Next = url + query.ToQueryString(); } diff --git a/Kyoo.Common/Models/People.cs b/Kyoo.Common/Models/People.cs index 6514dc9f..02c967b0 100644 --- a/Kyoo.Common/Models/People.cs +++ b/Kyoo.Common/Models/People.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class People + public class People : IRessource { public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/ProviderID.cs b/Kyoo.Common/Models/ProviderID.cs index b68785b1..b7e6479a 100644 --- a/Kyoo.Common/Models/ProviderID.cs +++ b/Kyoo.Common/Models/ProviderID.cs @@ -2,9 +2,10 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class ProviderID + public class ProviderID : IRessource { [JsonIgnore] public int ID { get; set; } + public string Slug => Name; public string Name { get; set; } public string Logo { get; set; } diff --git a/Kyoo.Common/Models/Season.cs b/Kyoo.Common/Models/Season.cs index 8d4d9052..1307bab3 100644 --- a/Kyoo.Common/Models/Season.cs +++ b/Kyoo.Common/Models/Season.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Season + public class Season : IRessource { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int ShowID { get; set; } diff --git a/Kyoo.Common/Models/Show.cs b/Kyoo.Common/Models/Show.cs index 959856ee..5b71b7a4 100644 --- a/Kyoo.Common/Models/Show.cs +++ b/Kyoo.Common/Models/Show.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Show : IOnMerge + public class Show : IRessource, IOnMerge { [JsonIgnore] public int ID { get; set; } diff --git a/Kyoo.Common/Models/Studio.cs b/Kyoo.Common/Models/Studio.cs index ec37121a..02d61c77 100644 --- a/Kyoo.Common/Models/Studio.cs +++ b/Kyoo.Common/Models/Studio.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Studio + public class Studio : IRessource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Track.cs b/Kyoo.Common/Models/Track.cs index 0e3549e0..a0688c32 100644 --- a/Kyoo.Common/Models/Track.cs +++ b/Kyoo.Common/Models/Track.cs @@ -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 EpisodeID { get; set; } diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 4fe2006e..6cb38734 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -225,79 +225,7 @@ namespace Kyoo yield return ret; } } - - public static string GetMemberName(Expression> 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 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> ParseWhere(Dictionary 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>(expression!, param); - } - public static string ToQueryString(this Dictionary query) { if (!query.Any()) diff --git a/Kyoo.CommonAPI/ApiHelper.cs b/Kyoo.CommonAPI/ApiHelper.cs new file mode 100644 index 00000000..7486b163 --- /dev/null +++ b/Kyoo.CommonAPI/ApiHelper.cs @@ -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 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> ParseWhere(Dictionary 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>(expression!, param); + } + } +} \ No newline at end of file diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs new file mode 100644 index 00000000..981b5d39 --- /dev/null +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -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 : ControllerBase where T : IRessource + { + private readonly IRepository _repository; + private readonly string _baseURL; + + public CrudApi(IRepository repository, IConfiguration configuration) + { + _repository = repository; + _baseURL = configuration.GetValue("public_url").TrimEnd('/'); + } + + [HttpGet("{id}")] + [Authorize(Policy = "Read")] + [JsonDetailed] + public async Task> 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> Get(string slug) + { + T ressource = await _repository.Get(slug); + if (ressource == null) + return NotFound(); + + return ressource; + } + + [HttpGet] + [Authorize(Policy = "Read")] + public async Task>> GetAll([FromQuery] string sortBy, + [FromQuery] int limit, + [FromQuery] int afterID, + [FromQuery] Dictionary where) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + if (limit == 0) + limit = 20; + + try + { + ICollection ressources = await _repository.GetAll(ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return new Page(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> 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> 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> 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 Delete(int id) + { + try + { + await _repository.Delete(id); + } + catch (ItemNotFound) + { + return NotFound(); + } + + return Ok(); + } + + [HttpDelete("{slug}")] + [Authorize(Policy = "Write")] + public async Task Delete(string slug) + { + try + { + await _repository.Delete(slug); + } + catch (ItemNotFound) + { + return NotFound(); + } + + return Ok(); + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs similarity index 100% rename from Kyoo/Views/API/JsonSerializer.cs rename to Kyoo.CommonAPI/JsonSerializer.cs diff --git a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj new file mode 100644 index 00000000..8e7ca25d --- /dev/null +++ b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + Kyoo.CommonApi + Kyoo.CommonApi + Kyoo.CommonApi + AnonymusRaccoon + + + + + + + + + + + + + + diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs new file mode 100644 index 00000000..9356d6c3 --- /dev/null +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -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 : IRepository where T : class, IRessource + { + private readonly DbContext _database; + + protected abstract Expression> DefaultSort { get; } + + + protected LocalRepository(DbContext database) + { + _database = database; + } + + public virtual void Dispose() + { + _database.Dispose(); + } + + public virtual ValueTask DisposeAsync() + { + return _database.DisposeAsync(); + } + + public virtual Task Get(int id) + { + return _database.Set().FirstOrDefaultAsync(x => x.ID == id); + } + + public virtual Task Get(string slug) + { + return _database.Set().FirstOrDefaultAsync(x => x.Slug == slug); + } + + public abstract Task> Search(string query); + + public virtual async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + IQueryable query = _database.Set(); + + if (where != null) + query = query.Where(where); + + Expression> 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>( + 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 Create(T obj); + + public virtual async Task 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 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 objs) + { + foreach (T obj in objs) + await Delete(obj); + } + + public virtual async Task DeleteRange(IEnumerable ids) + { + foreach (int id in ids) + await Delete(id); + } + + public virtual async Task DeleteRange(IEnumerable 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; + } + } +} \ No newline at end of file diff --git a/Kyoo.sln b/Kyoo.sln index 12f6f2ad..cc3d4222 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -3,6 +3,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo", "Kyoo\Kyoo.csproj", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\Kyoo.Common.csproj", "{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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}.Release|Any CPU.ActiveCfg = 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 EndGlobal diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 955abc2d..a4966407 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -9,37 +9,17 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class CollectionRepository : ICollectionRepository + public class CollectionRepository : LocalRepository, ICollectionRepository { private readonly DatabaseContext _database; + protected override Expression> DefaultSort => x => x.Name; - - public CollectionRepository(DatabaseContext database) + public CollectionRepository(DatabaseContext database) : base(database) { _database = database; } - public void Dispose() - { - _database.Dispose(); - } - - public ValueTask DisposeAsync() - { - return _database.DisposeAsync(); - } - - public Task Get(int id) - { - return _database.Collections.FirstOrDefaultAsync(x => x.ID == id); - } - - public Task Get(string slug) - { - return _database.Collections.FirstOrDefaultAsync(x => x.Slug == slug); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Collections .Where(x => EF.Functions.Like(x.Name, $"%{query}%")) @@ -47,32 +27,7 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - IQueryable query = _database.Collections; - - if (where != null) - query = query.Where(where); - - Expression> 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>( - Expression.GreaterThan(sortKey, Expression.Constant(afterObj)) - )); - } - query = query.Take(page.Count <= 0 ? 20 : page.Count); - - return await query.ToListAsync(); - } - - public async Task Create(Collection obj) + public override async Task Create(Collection obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -86,66 +41,20 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); throw; } return obj; } - - public async Task CreateIfNotExists(Collection obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Collection 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; - } + protected override Task Validate(Collection ressource) + { + return Task.CompletedTask; } - public async Task Edit(Collection edited, bool resetOld) - { - 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) + public override async Task Delete(Collection obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -159,23 +68,5 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Collection obj in objs) - await Delete(obj); - } - - public async Task DeleteRange(IEnumerable ids) - { - foreach (int id in ids) - await Delete(id); - } - - public async Task DeleteRange(IEnumerable slugs) - { - foreach (string slug in slugs) - await Delete(slug); - } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index eddb63bd..6f9fd900 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -10,35 +10,33 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class EpisodeRepository : IEpisodeRepository + public class EpisodeRepository : LocalRepository, IEpisodeRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; - // private readonly ITrackRepository _tracks; + protected override Expression> DefaultSort => x => x.EpisodeNumber; - public EpisodeRepository(DatabaseContext database, IProviderRepository providers) + public EpisodeRepository(DatabaseContext database, IProviderRepository providers) : base(database) { _database = database; _providers = providers; } - - public void Dispose() + + + public override void Dispose() { _database.Dispose(); + _providers.Dispose(); } - public ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { - return _database.DisposeAsync(); - } - - public Task Get(int id) - { - return _database.Episodes.FirstOrDefaultAsync(x => x.ID == id); + await _database.DisposeAsync(); + await _providers.DisposeAsync(); } - public Task Get(string slug) + public override Task Get(string slug) { Match match = Regex.Match(slug, @"(.*)-s(\d*)-e(\d*)"); @@ -56,7 +54,7 @@ namespace Kyoo.Controllers && x.EpisodeNumber == episodeNumber); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Episodes .Where(x => EF.Functions.Like(x.Title, $"%{query}%")) @@ -64,14 +62,7 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return await _database.Episodes.ToListAsync(); - } - - public async Task Create(Episode obj) + public override async Task Create(Episode obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -82,7 +73,6 @@ namespace Kyoo.Controllers foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Added; - // Since Episodes & Tracks are on the same DB, using a single commit is quicker. if (obj.Tracks != null) foreach (Track entry in obj.Tracks) _database.Entry(entry).State = EntityState.Added; @@ -95,64 +85,15 @@ namespace Kyoo.Controllers { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); 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; } - - public async Task CreateIfNotExists(Episode obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Episode 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 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) + protected override async Task Validate(Episode obj) { if (obj.ShowID <= 0) 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(); } - 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) { Episode obj = await Get(showSlug, seasonNumber, episodeNumber); await Delete(obj); } - public async Task Delete(Episode obj) + public override async Task Delete(Episode obj) { if (obj == null) 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. await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Episode obj in objs) - await Delete(obj); - } - - public async Task DeleteRange(IEnumerable ids) - { - foreach (int id in ids) - await Delete(id); - } - - public async Task DeleteRange(IEnumerable slugs) - { - foreach (string slug in slugs) - await Delete(slug); - } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index bafa9226..9b2abcb1 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -9,35 +9,17 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class GenreRepository : IGenreRepository + public class GenreRepository : LocalRepository, IGenreRepository { private readonly DatabaseContext _database; + protected override Expression> DefaultSort => x => x.Slug; - public GenreRepository(DatabaseContext database) + public GenreRepository(DatabaseContext database) : base(database) { _database = database; } - public void Dispose() - { - _database.Dispose(); - } - - public ValueTask DisposeAsync() - { - return _database.DisposeAsync(); - } - - public async Task Get(int id) - { - return await _database.Genres.FirstOrDefaultAsync(x => x.ID == id); - } - - public async Task Get(string slug) - { - return await _database.Genres.FirstOrDefaultAsync(x => x.Slug == slug); - } public async Task> Search(string query) { @@ -47,14 +29,7 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination page = default) - { - return await _database.Genres.ToListAsync(); - } - - public async Task Create(Genre obj) + public override async Task Create(Genre obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -69,7 +44,7 @@ namespace Kyoo.Controllers { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated genre (slug {obj.Slug} already exists)."); throw; } @@ -77,54 +52,9 @@ namespace Kyoo.Controllers return obj; } - public async Task CreateIfNotExists(Genre obj) + protected override Task Validate(Genre ressource) { - if (obj == null) - 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 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); + return Task.CompletedTask; } public async Task Delete(Genre obj) @@ -138,23 +68,5 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Genre obj in objs) - await Delete(obj); - } - - public async Task DeleteRange(IEnumerable ids) - { - foreach (int id in ids) - await Delete(id); - } - - public async Task DeleteRange(IEnumerable slugs) - { - foreach (string slug in slugs) - await Delete(slug); - } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/Helper.cs b/Kyoo/Controllers/Repositories/Helper.cs deleted file mode 100644 index 2881fe77..00000000 --- a/Kyoo/Controllers/Repositories/Helper.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 4967fa0b..4ae6757a 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -51,7 +51,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Libraries.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 5dad3c4c..7798cd8b 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -50,7 +50,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Peoples.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index ca82638e..be8f3989 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -49,7 +49,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Providers.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 61ed44b2..045599b8 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -65,7 +65,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Seasons.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 36ff0c39..db1b7b83 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -3,6 +3,7 @@ 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; @@ -73,7 +74,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { IQueryable query = _database.Shows; @@ -83,17 +84,17 @@ namespace Kyoo.Controllers Expression> sortKey = sort.Key ?? (x => x.Title); 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); query = query.Where(Expression.Lambda>( - Utility.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)), + ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)), (ParameterExpression)((MemberExpression)sortKey.Body).Expression )); } - if (page.Count > 0) - query = query.Take(page.Count); + if (limit.Count > 0) + query = query.Take(limit.Count); return await query.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index dda47a2a..3dc06439 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -49,7 +49,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Studios.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 74fd4bf9..5362c63d 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -52,7 +52,7 @@ namespace Kyoo.Controllers public async Task> GetAll(Expression> where = null, Sort sort = default, - Pagination page = default) + Pagination limit = default) { return await _database.Tracks.ToListAsync(); } diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index 69d8d8df..76fff030 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -44,6 +44,7 @@ + diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 4b2a8f46..34468068 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -224,7 +224,8 @@ namespace Kyoo.Controllers bool isMovie, Library library) { - Show show = await libraryManager.GetShowByPath(showPath); + Show show = (await libraryManager.GetShows(x => x.Path == showPath, limit: 1)) + .FirstOrDefault(); if (show != null) return show; show = await _metadataProvider.SearchShow(showTitle, isMovie, library); diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index 76d2754d..abea4b1e 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -16,167 +16,6 @@ namespace Kyoo.API [ApiController] 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("public_url").TrimEnd('/'); - } - - [HttpGet] - [Authorize(Policy="Read")] - public async Task>> GetShows([FromQuery] string sortBy, - [FromQuery] int limit, - [FromQuery] int afterID, - [FromQuery] Dictionary where) - { - where.Remove("sortBy"); - where.Remove("limit"); - where.Remove("afterID"); - if (limit == 0) - limit = 20; - - try - { - ICollection shows = await _shows.GetAll(Utility.ParseWhere(where), - new Sort(sortBy), - new Pagination(limit, afterID)); - - return new Page(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> 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> GetShow(string slug) - { - Show show = await _shows.Get(slug); - if (show == null) - return NotFound(); - - return show; - } - - [HttpPost] - [Authorize(Policy="Write")] - public async Task> 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> 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 DeleteShow(string slug) - { - try - { - await _shows.Delete(slug); - } - catch (ItemNotFound) - { - return NotFound(); - } - return Ok(); - } - - [HttpDelete("{id}")] - // [Authorize(Policy="Write")] - public async Task 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 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> IdentityShow(string name, [FromQuery] bool isMovie) - { - return await _providerManager.SearchShows(name, isMovie, null); - } - - [HttpPost("download-images/{slug}")] - [Authorize(Policy = "Write")] - public async Task DownloadImages(string slug) - { - Show show = await _libraryManager.GetShow(slug); - if (show == null) - return NotFound(); - await _thumbnailsManager.Validate(show, true); - return Ok(); - } } }