From 3eaf4c005a2e8c9dd1e9cb39ce3dc7d935a014b7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Jun 2020 21:56:05 +0200 Subject: [PATCH 01/36] Starting the GetAll of the collection repository --- Kyoo.Common/Controllers/IRepository.cs | 32 ++++++++----------- .../Repositories/CollectionRepository.cs | 12 ++++++- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 22f47a16..e401bca3 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -8,33 +8,28 @@ using Kyoo.Models; namespace Kyoo.Controllers { - public struct Pagination + public readonly struct Pagination { - public int Count; - public int AfterID; + public int Count { get; } + public int AfterID { get; } + + public Pagination(int count, int afterID = 0) + { + Count = count; + AfterID = afterID; + } } public readonly struct Sort { - public string Key { get; } + public Expression> Key { get; } public bool Descendant { get; } - - public Sort(string key, bool descendant = false) + + public Sort(Expression> key, bool descendant = false) { Key = key; Descendant = descendant; } - - public Sort(Expression> key) - { - Key = Utility.GetMemberName(key); - Descendant = false; - } - - public static implicit operator Sort([NotNull] Expression> key) - { - return new Sort(Utility.GetMemberName(key)); - } } public interface IRepository : IDisposable, IAsyncDisposable @@ -49,7 +44,8 @@ namespace Kyoo.Controllers Task> GetAll(Expression> where = null, Expression> sort = default, - Pagination page = default) => GetAll(where, new Sort(sort), page); + Pagination page = default + ) => GetAll(where, new Sort(sort), page); Task Create([NotNull] T obj); Task CreateIfNotExists([NotNull] T obj); diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index d183d3d6..e94c34b8 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -51,7 +51,17 @@ namespace Kyoo.Controllers Sort sort = default, Pagination page = default) { - return await _database.Collections.ToListAsync(); + IQueryable query = _database.Collections; + + if (where != null) + query = query.Where(where); + + Expression> sortOrder = sort.Key ?? (x => x.Name); + query = sort.Descendant ? query.OrderByDescending(sortOrder) : query.OrderBy(sortOrder); + + query.Where(x => x.ID ) + + return await query.ToListAsync(); } public async Task Create(Collection obj) From 38bb0a2efe2103f98a29f98fb5859bf4bd28b9f1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Jun 2020 23:30:10 +0200 Subject: [PATCH 02/36] Adding parameters everywhere --- Kyoo.Common/Controllers/ILibraryManager.cs | 86 ++++++++++++++++--- Kyoo.Common/Controllers/IRepository.cs | 5 +- Kyoo/Controllers/LibraryManager.cs | 62 ++++++++----- .../Repositories/CollectionRepository.cs | 18 ++-- .../Repositories/EpisodeRepository.cs | 5 +- .../Repositories/GenreRepository.cs | 5 +- .../Repositories/LibraryRepository.cs | 5 +- .../Repositories/PeopleRepository.cs | 5 +- .../Repositories/ProviderRepository.cs | 5 +- .../Repositories/SeasonRepository.cs | 5 +- .../Repositories/ShowRepository.cs | 5 +- .../Repositories/StudioRepository.cs | 5 +- .../Repositories/TrackRepository.cs | 5 +- 13 files changed, 168 insertions(+), 48 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 72c211ae..69302e21 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; +using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models; @@ -36,18 +38,80 @@ namespace Kyoo.Controllers Task AddShowLink([NotNull] Show show, Library library, Collection collection); // Get all - Task> GetLibraries(); - Task> GetCollections(); - Task> GetShows(); - Task> GetSeasons(); - Task> GetEpisodes(); - Task> GetTracks(); - Task> GetStudios(); - Task> GetPeoples(); - Task> GetGenres(); - Task> GetProviders(); + Task> GetLibraries(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetCollections(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetShows(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetSeasons(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetEpisodes(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetTracks(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetStudios(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetPeople(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetGenres(Expression> where = null, + Sort sort = default, + Pagination page = default); + Task> GetProviders(Expression> where = null, + Sort sort = default, + Pagination page = default); + + Task> GetLibraries([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetLibraries(where, new Sort(sort), page); + Task> GetCollections([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetCollections(where, new Sort(sort), page); + Task> GetShows([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetShows(where, new Sort(sort), page); + Task> GetSeasons([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetSeasons(where, new Sort(sort), page); + Task> GetEpisodes([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetEpisodes(where, new Sort(sort), page); + Task> GetTracks([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetTracks(where, new Sort(sort), page); + Task> GetStudios([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetStudios(where, new Sort(sort), page); + Task> GetPeople([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetPeople(where, new Sort(sort), page); + Task> GetGenres([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetGenres(where, new Sort(sort), page); + Task> GetProviders([Optional] Expression> where, + Expression> sort, + Pagination page = default + ) => GetProviders(where, new Sort(sort), page); - // Search + + // Search Task> SearchLibraries(string searchQuery); Task> SearchCollections(string searchQuery); Task> SearchShows(string searchQuery); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index e401bca3..5f9fe3ae 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.Models; @@ -42,8 +43,8 @@ namespace Kyoo.Controllers Sort sort = default, Pagination page = default); - Task> GetAll(Expression> where = null, - Expression> sort = default, + Task> GetAll([Optional] Expression> where, + Expression> sort, Pagination page = default ) => GetAll(where, new Sort(sort), page); diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs index 8092f5c6..6aa10c4e 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo/Controllers/LibraryManager.cs @@ -127,54 +127,74 @@ namespace Kyoo.Controllers return _people.Get(slug); } - public Task> GetLibraries() + public Task> GetLibraries(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _libraries.GetAll(); + return _libraries.GetAll(where, sort, page); } - public Task> GetCollections() + public Task> GetCollections(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _collections.GetAll(); + return _collections.GetAll(where, sort, page); } - public Task> GetShows() + public Task> GetShows(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _shows.GetAll(); + return _shows.GetAll(where, sort, page); } - public Task> GetSeasons() + public Task> GetSeasons(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _seasons.GetAll(); + return _seasons.GetAll(where, sort, page); } - public Task> GetEpisodes() + public Task> GetEpisodes(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _episodes.GetAll(); + return _episodes.GetAll(where, sort, page); } - public Task> GetTracks() + public Task> GetTracks(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _tracks.GetAll(); + return _tracks.GetAll(where, sort, page); } - public Task> GetStudios() + public Task> GetStudios(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _studios.GetAll(); + return _studios.GetAll(where, sort, page); } - public Task> GetPeoples() + public Task> GetPeoples(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _people.GetAll(); + return _people.GetAll(where, sort, page); } - public Task> GetGenres() + public Task> GetGenres(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _genres.GetAll(); + return _genres.GetAll(where, sort, page); } - public Task> GetProviders() + public Task> GetProviders(Expression> where = null, + Sort sort = default, + Pagination page = default) { - return _providers.GetAll(); + return _providers.GetAll(where, sort, page); } public Task> GetSeasons(int showID) @@ -364,7 +384,7 @@ namespace Kyoo.Controllers return _shows.Delete(show); } - public Task DeleteSeason(Season season, IShowRepository repo) + public Task DeleteSeason(Season season) { return _seasons.Delete(season); } diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index e94c34b8..e9253b07 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -56,11 +56,19 @@ namespace Kyoo.Controllers if (where != null) query = query.Where(where); - Expression> sortOrder = sort.Key ?? (x => x.Name); - query = sort.Descendant ? query.OrderByDescending(sortOrder) : query.OrderBy(sortOrder); - - query.Where(x => x.ID ) - + 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(); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index e4d54134..9781e11a 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -69,7 +70,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Episodes.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index 9834e37e..b6ee440d 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -46,7 +47,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Genres.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 99e15a0f..acd20b43 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -48,7 +49,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Libraries.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 85664970..df5f81c6 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -47,7 +48,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Peoples.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index ec298411..5b946139 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -46,7 +47,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Providers.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index c9433c72..ef78914f 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -62,7 +63,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Seasons.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 20bc7f67..378c724c 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -70,7 +71,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Shows.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 69e93e74..4e7583cb 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -46,7 +47,9 @@ namespace Kyoo.Controllers .ToListAsync(); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Studios.ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index dbc54b01..c5731c0c 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -49,7 +50,9 @@ namespace Kyoo.Controllers throw new InvalidOperationException("Tracks do not support the search method."); } - public async Task> GetAll() + public async Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination page = default) { return await _database.Tracks.ToListAsync(); } From 3443e102e947b56b13a2ac78f26ad6d05096f710 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 28 Jun 2020 23:30:50 +0200 Subject: [PATCH 03/36] Fixing a typo --- Kyoo/Controllers/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo/Controllers/LibraryManager.cs index 6aa10c4e..cf65dc8d 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo/Controllers/LibraryManager.cs @@ -176,7 +176,7 @@ namespace Kyoo.Controllers return _studios.GetAll(where, sort, page); } - public Task> GetPeoples(Expression> where = null, + public Task> GetPeople(Expression> where = null, Sort sort = default, Pagination page = default) { From ea625fa45bb01f4dffb44d5cb30b8f40631645ab Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 Jun 2020 00:12:06 +0200 Subject: [PATCH 04/36] Implementing a basic query parser for where, sort & pagination --- Kyoo.Common/Controllers/IRepository.cs | 21 +++++++++ Kyoo.Common/Utility.cs | 44 +++++++++++++++++++ .../Repositories/ShowRepository.cs | 20 ++++++++- Kyoo/Views/API/ShowsAPI.cs | 17 ++++--- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 5f9fe3ae..9ceb0bd7 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -31,6 +31,27 @@ namespace Kyoo.Controllers Key = key; Descendant = descendant; } + + public Sort(string sortBy) + { + if (string.IsNullOrEmpty(sortBy)) + { + Key = null; + Descendant = false; + return; + } + + string key = sortBy.Contains(':') ? sortBy.Substring(0, sortBy.IndexOf(':')) : sortBy; + string order = sortBy.Contains(':') ? sortBy.Substring(sortBy.IndexOf(':') + 1) : null; + + Key = Expression.Lambda>(Expression.Property(Expression.Parameter(typeof(T), "x"), key)); + Descendant = order switch + { + "desc" => true, + "asc" => false, + _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") + }; + } } public interface IRepository : IDisposable, IAsyncDisposable diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 23313077..904427f3 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -236,5 +236,49 @@ namespace Kyoo return member.Member.Name; } + + public static Expression> ParseWhere(Dictionary where) + { + if (where == null || where.Count == 0) + return null; + + ParameterExpression param = Expression.Parameter(typeof(T)); + Expression expression = null; + + foreach (KeyValuePair cond in where) + { + string value = cond.Value; + string operand = "eq"; + if (value.Contains(':')) + { + operand = value.Substring(0, value.IndexOf(':')); + value = value.Substring(value.IndexOf(':') + 1); + } + + PropertyInfo valueParam = typeof(T).GetProperty(cond.Key); // TODO get this property with case insensitive. + // TODO throw if valueParam is null. + MemberExpression property = Expression.Property(param, valueParam); + ConstantExpression condValue = Expression.Constant(value); // TODO Cast this to the right type (take nullable into account). + + Expression condition = operand switch + { + "eq" => Expression.Equal(property, condValue), + "not" => Expression.NotEqual(property, condValue), + "lt" => Expression.LessThan(property, condValue), + "lte" => Expression.LessThanOrEqual(property, condValue), + "gt" => Expression.GreaterThan(property, condValue), + "gte" => Expression.GreaterThanOrEqual(property, condValue), + "like" => throw new NotImplementedException("Like not implemented yet"), + _ => throw new ArgumentException($"Invalid operand: {operand}") + }; + + if (expression != null) + expression = Expression.AndAlso(expression, condition); + else + expression = condition; + } + + return Expression.Lambda>(expression!); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 378c724c..4ac185fa 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -75,7 +75,25 @@ namespace Kyoo.Controllers Sort sort = default, Pagination page = default) { - return await _database.Shows.ToListAsync(); + IQueryable query = _database.Shows; + + if (where != null) + query = query.Where(where); + + Expression> sortKey = sort.Key ?? (x => x.Title); + query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); + + if (page.AfterID != 0) + { + Show after = await Get(page.AfterID); + object afterObj = sortKey.Compile()(after); + query = query.Where(Expression.Lambda>( + Expression.GreaterThan(sortKey, Expression.Constant(afterObj)) + )); + } + query = query.Take(page.Count <= 0 ? 20 : page.Count); + + return await query.ToListAsync(); } public async Task Create(Show obj) diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index a22a485e..0ce7fa33 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -1,8 +1,11 @@ -using Kyoo.Models; +using System; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; +using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; @@ -35,12 +38,14 @@ namespace Kyoo.Api [HttpGet] [Authorize(Policy="Read")] - public IEnumerable GetShows() + public async Task> GetShows([FromQuery] string sortBy, + [FromQuery] int limit, + [FromQuery] int afterID, + [FromQuery] Dictionary where) { - return _database.LibraryLinks - .Include(x => x.Show) - .Include(x => x.Collection) - .AsEnumerable().Select(x => x.Show ?? x.Collection.AsShow()).ToList(); + return await _libraryManager.GetShows(Utility.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); } [HttpGet("{slug}")] From 1da26b449b523dac5d4222f1ad58c7f721d13112 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 30 Jun 2020 13:00:06 +0200 Subject: [PATCH 05/36] Completing the where translater --- Kyoo.Common/Utility.cs | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 904427f3..e10972de 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -245,29 +245,31 @@ namespace Kyoo ParameterExpression param = Expression.Parameter(typeof(T)); Expression expression = null; - foreach (KeyValuePair cond in where) + foreach ((string key, string desired) in where) { - string value = cond.Value; + string value = desired; string operand = "eq"; - if (value.Contains(':')) + if (desired.Contains(':')) { - operand = value.Substring(0, value.IndexOf(':')); - value = value.Substring(value.IndexOf(':') + 1); + operand = desired.Substring(0, desired.IndexOf(':')); + value = desired.Substring(desired.IndexOf(':') + 1); } - PropertyInfo valueParam = typeof(T).GetProperty(cond.Key); // TODO get this property with case insensitive. - // TODO throw if valueParam is null. - MemberExpression property = Expression.Property(param, valueParam); - ConstantExpression condValue = Expression.Constant(value); // TODO Cast this to the right type (take nullable into account). + 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); + ConstantExpression valueExpr = Expression.Constant(Convert.ChangeType(value, property.PropertyType)); Expression condition = operand switch { - "eq" => Expression.Equal(property, condValue), - "not" => Expression.NotEqual(property, condValue), - "lt" => Expression.LessThan(property, condValue), - "lte" => Expression.LessThanOrEqual(property, condValue), - "gt" => Expression.GreaterThan(property, condValue), - "gte" => Expression.GreaterThanOrEqual(property, condValue), + "eq" => Expression.Equal(propertyExpr, valueExpr), + "not" => Expression.NotEqual(propertyExpr, valueExpr), + "lt" => Expression.LessThan(propertyExpr, valueExpr), + "lte" => Expression.LessThanOrEqual(propertyExpr, valueExpr), + "gt" => Expression.GreaterThan(propertyExpr, valueExpr), + "gte" => Expression.GreaterThanOrEqual(propertyExpr, valueExpr), + // TODO Implement the Like expression "like" => throw new NotImplementedException("Like not implemented yet"), _ => throw new ArgumentException($"Invalid operand: {operand}") }; From 4e9096ae76aee771927fc1905e969be0dd4775c9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 3 Jul 2020 17:10:27 +0200 Subject: [PATCH 06/36] Solving the afterID lambda --- Kyoo.Common/Utility.cs | 27 +++++++++++++++---- .../Repositories/ShowRepository.cs | 3 ++- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/ShowsAPI.cs | 9 ++++--- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index e10972de..40e3b653 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -237,6 +237,18 @@ namespace Kyoo 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) @@ -259,16 +271,21 @@ namespace Kyoo if (property == null) throw new ArgumentException($"No filterable parameter with the name {key}."); MemberExpression propertyExpr = Expression.Property(param, property); - ConstantExpression valueExpr = Expression.Constant(Convert.ChangeType(value, property.PropertyType)); + + 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); Expression condition = operand switch { "eq" => Expression.Equal(propertyExpr, valueExpr), "not" => Expression.NotEqual(propertyExpr, valueExpr), - "lt" => Expression.LessThan(propertyExpr, valueExpr), - "lte" => Expression.LessThanOrEqual(propertyExpr, valueExpr), - "gt" => Expression.GreaterThan(propertyExpr, valueExpr), - "gte" => Expression.GreaterThanOrEqual(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), // TODO Implement the Like expression "like" => throw new NotImplementedException("Like not implemented yet"), _ => throw new ArgumentException($"Invalid operand: {operand}") diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 4ac185fa..cd67da1e 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -88,7 +88,8 @@ namespace Kyoo.Controllers Show after = await Get(page.AfterID); object afterObj = sortKey.Compile()(after); query = query.Where(Expression.Lambda>( - Expression.GreaterThan(sortKey, Expression.Constant(afterObj)) + Utility.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)), + (ParameterExpression)((MemberExpression)sortKey.Body).Expression )); } query = query.Take(page.Count <= 0 ? 20 : page.Count); diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 3e45db4b..c406472c 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -39,7 +39,7 @@ namespace Kyoo { configuration.RootPath = "wwwroot"; }); - + services.AddControllers().AddNewtonsoftJson(); services.AddHttpClient(); diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index 0ce7fa33..7426f08a 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -1,11 +1,8 @@ -using System; -using Kyoo.Models; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; -using Castle.DynamicProxy.Generators.Emitters.SimpleAST; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; @@ -43,6 +40,10 @@ namespace Kyoo.Api [FromQuery] int afterID, [FromQuery] Dictionary where) { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + return await _libraryManager.GetShows(Utility.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); From 6bdd363b7b7879fede4706c01b59a042ae251aa7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 5 Jul 2020 18:29:16 +0200 Subject: [PATCH 07/36] Making where & pagination works --- Kyoo.Common/Controllers/IRepository.cs | 5 +- Kyoo.Common/Models/Page.cs | 50 +++++++++++++++++++ Kyoo.Common/Utility.cs | 11 +++- .../Repositories/EpisodeRepository.cs | 20 +++----- .../Repositories/ShowRepository.cs | 2 +- Kyoo/Views/API/ShowsAPI.cs | 35 ++++++++++--- 6 files changed, 99 insertions(+), 24 deletions(-) create mode 100644 Kyoo.Common/Models/Page.cs diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 9ceb0bd7..2b2dbd22 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -25,11 +25,14 @@ namespace Kyoo.Controllers { public Expression> Key { get; } public bool Descendant { get; } - + public Sort(Expression> key, bool descendant = false) { Key = key; Descendant = descendant; + + if (!(Key.Body is MemberExpression)) + throw new ArgumentException("The given sort key is not valid."); } public Sort(string sortBy) diff --git a/Kyoo.Common/Models/Page.cs b/Kyoo.Common/Models/Page.cs new file mode 100644 index 00000000..3f751613 --- /dev/null +++ b/Kyoo.Common/Models/Page.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Kyoo.Models +{ + public class Page + { + public string This { get; set; } + public string First { get; set; } + public string Next { get; set; } + + public int Count => Items.Count; + public ICollection Items { get; set; } + + public Page() { } + + public Page(ICollection items) + { + Items = items; + } + + public Page(ICollection items, string @this, string next, string first) + { + Items = items; + This = @this; + Next = next; + First = first; + } + + public Page(ICollection items, + Func getID, + string url, + Dictionary query, + int limit) + { + Items = items; + This = url + query.ToQueryString(); + + if (items.Count == limit) + { + query["afterID"] = getID(items.Last()); + Next = url + query.ToQueryString(); + } + + query.Remove("afterID"); + First = url + query.ToQueryString(); + } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 40e3b653..31afb1a7 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -276,7 +276,7 @@ namespace Kyoo object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase) ? null : Convert.ChangeType(value, propertyType); - ConstantExpression valueExpr = Expression.Constant(val); + ConstantExpression valueExpr = Expression.Constant(val, property.PropertyType); Expression condition = operand switch { @@ -297,7 +297,14 @@ namespace Kyoo expression = condition; } - return Expression.Lambda>(expression!); + return Expression.Lambda>(expression!, param); + } + + public static string ToQueryString(this Dictionary query) + { + if (!query.Any()) + return string.Empty; + return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}")); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 9781e11a..90977937 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -39,20 +40,13 @@ namespace Kyoo.Controllers public Task Get(string slug) { - int sIndex = slug.IndexOf("-s", StringComparison.Ordinal); - int eIndex = slug.IndexOf("-e", StringComparison.Ordinal); - - if (sIndex == -1 && eIndex == -1) - return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); + Match match = Regex.Match(slug, @"(.*)-s(\d*)-e(\d*)"); - if (sIndex == -1 || eIndex == -1 || eIndex < sIndex) - throw new InvalidOperationException("Invalid episode slug. Format: {showSlug}-s{seasonNumber}-e{episodeNumber}"); - string showSlug = slug.Substring(0, sIndex); - if (!int.TryParse(slug.Substring(sIndex + 2), out int seasonNumber)) - throw new InvalidOperationException("Invalid episode slug. Format: {showSlug}-s{seasonNumber}-e{episodeNumber}"); - if (!int.TryParse(slug.Substring(eIndex + 2), out int episodeNumber)) - throw new InvalidOperationException("Invalid episode slug. Format: {showSlug}-s{seasonNumber}-e{episodeNumber}"); - return Get(showSlug, seasonNumber, episodeNumber); + if (!match.Success) + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); + return Get(match.Groups["show"].Value, + int.Parse(match.Groups["season"].Value), + int.Parse(match.Groups["episode"].Value)); } public Task Get(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index cd67da1e..b6b611cb 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -92,7 +92,7 @@ namespace Kyoo.Controllers (ParameterExpression)((MemberExpression)sortKey.Body).Expression )); } - query = query.Take(page.Count <= 0 ? 20 : page.Count); + query = query.Take(page.Count); return await query.ToListAsync(); } diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index 7426f08a..d89625f8 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -1,4 +1,5 @@ -using Kyoo.Models; +using System; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; @@ -6,6 +7,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; namespace Kyoo.Api { @@ -19,23 +21,26 @@ namespace Kyoo.Api private readonly DatabaseContext _database; private readonly IThumbnailsManager _thumbnailsManager; private readonly ITaskManager _taskManager; + private readonly string _baseURL; public ShowsAPI(ILibraryManager libraryManager, IProviderManager providerManager, DatabaseContext database, IThumbnailsManager thumbnailsManager, - ITaskManager taskManager) + ITaskManager taskManager, + IConfiguration configuration) { _libraryManager = libraryManager; _providerManager = providerManager; _database = database; _thumbnailsManager = thumbnailsManager; _taskManager = taskManager; + _baseURL = configuration.GetValue("public_url").TrimEnd('/'); } [HttpGet] [Authorize(Policy="Read")] - public async Task> GetShows([FromQuery] string sortBy, + public async Task>> GetShows([FromQuery] string sortBy, [FromQuery] int limit, [FromQuery] int afterID, [FromQuery] Dictionary where) @@ -43,10 +48,26 @@ namespace Kyoo.Api where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); - - return await _libraryManager.GetShows(Utility.ParseWhere(where), - new Sort(sortBy), - new Pagination(limit, afterID)); + if (limit <= 0) + limit = 20; + + ICollection shows; + try + { + shows = await _libraryManager.GetShows(Utility.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + } + catch (ArgumentException ex) + { + return BadRequest(new { Error = ex.Message }); + } + + return new Page(shows, + x => $"{x.ID}", + _baseURL + Request.Path, + Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), + limit); } [HttpGet("{slug}")] From aa63c9e8f29b627b43559e4c9cd51479524b4468 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 5 Jul 2020 23:20:43 +0200 Subject: [PATCH 08/36] Cleaning up --- Kyoo.Common/Utility.cs | 4 +--- Kyoo/Controllers/Repositories/ShowRepository.cs | 3 ++- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/ShowsAPI.cs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 31afb1a7..4fe2006e 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -277,7 +277,7 @@ namespace Kyoo ? null : Convert.ChangeType(value, propertyType); ConstantExpression valueExpr = Expression.Constant(val, property.PropertyType); - + Expression condition = operand switch { "eq" => Expression.Equal(propertyExpr, valueExpr), @@ -286,8 +286,6 @@ namespace Kyoo "lte" => StringCompatibleExpression(Expression.LessThanOrEqual, propertyExpr, valueExpr), "gt" => StringCompatibleExpression(Expression.GreaterThan, propertyExpr, valueExpr), "gte" => StringCompatibleExpression(Expression.GreaterThanOrEqual, propertyExpr, valueExpr), - // TODO Implement the Like expression - "like" => throw new NotImplementedException("Like not implemented yet"), _ => throw new ArgumentException($"Invalid operand: {operand}") }; diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index b6b611cb..30b961f3 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -92,7 +92,8 @@ namespace Kyoo.Controllers (ParameterExpression)((MemberExpression)sortKey.Body).Expression )); } - query = query.Take(page.Count); + if (page.Count > 0) + query = query.Take(page.Count); return await query.ToListAsync(); } diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index c406472c..4c3984b9 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -48,7 +48,7 @@ namespace Kyoo options.UseLazyLoadingProxies() .UseNpgsql(_configuration.GetConnectionString("Database")); // .EnableSensitiveDataLogging() - // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + //.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index d89625f8..58f9d504 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -48,7 +48,7 @@ namespace Kyoo.Api where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); - if (limit <= 0) + if (limit == 0) limit = 20; ICollection shows; From 4357d599e421bac37145eda1b35f687272e29132 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 6 Jul 2020 02:20:05 +0200 Subject: [PATCH 09/36] Working on the sortBy --- Kyoo.Common/Controllers/IRepository.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 2b2dbd22..f7e45d20 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -46,12 +46,14 @@ namespace Kyoo.Controllers string key = sortBy.Contains(':') ? sortBy.Substring(0, sortBy.IndexOf(':')) : sortBy; string order = sortBy.Contains(':') ? sortBy.Substring(sortBy.IndexOf(':') + 1) : null; - - Key = Expression.Lambda>(Expression.Property(Expression.Parameter(typeof(T), "x"), key)); + + ParameterExpression param = Expression.Parameter(typeof(T), "x"); + Key = Expression.Lambda>(Expression.Property(param, key), param); Descendant = order switch { "desc" => true, "asc" => false, + "" => false, _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; } From 63a85124b7745d75da0481a10c6227128ef12ba9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 6 Jul 2020 20:38:34 +0200 Subject: [PATCH 10/36] Fixing the sort by --- Kyoo.Common/Controllers/IRepository.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index f7e45d20..34daa52a 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -31,8 +31,11 @@ namespace Kyoo.Controllers Key = key; Descendant = descendant; - if (!(Key.Body is MemberExpression)) - throw new ArgumentException("The given sort key is not valid."); + if (Key.Body is MemberExpression || + Key.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)Key.Body).Operand is MemberExpression) + return; + + throw new ArgumentException("The given sort key is not valid."); } public Sort(string sortBy) @@ -48,12 +51,16 @@ namespace Kyoo.Controllers string order = sortBy.Contains(':') ? sortBy.Substring(sortBy.IndexOf(':') + 1) : null; ParameterExpression param = Expression.Parameter(typeof(T), "x"); - Key = Expression.Lambda>(Expression.Property(param, key), param); + MemberExpression property = Expression.Property(param, key); + Key = property.Type.IsValueType + ? Expression.Lambda>(Expression.Convert(property, typeof(object)), param) + : Expression.Lambda>(property, param); + Descendant = order switch { "desc" => true, "asc" => false, - "" => false, + null => false, _ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.") }; } From d0117a8325bd991d46bebabaef29c66f88a6ac91 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 16 Jul 2020 21:25:22 +0200 Subject: [PATCH 11/36] Starting to clean up --- Kyoo.Common/Controllers/ILibraryManager.cs | 59 +++++--- Kyoo.Common/Controllers/IRepository.cs | 7 +- .../Implementations}/LibraryManager.cs | 133 ++++++++++++++---- .../Repositories/CollectionRepository.cs | 13 +- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/AccountAPI.cs | 2 +- Kyoo/Views/API/CollectionAPI.cs | 2 +- Kyoo/Views/API/EpisodesAPI.cs | 2 +- Kyoo/Views/API/LibrariesAPI.cs | 2 +- Kyoo/Views/API/PeopleAPI.cs | 2 +- Kyoo/Views/API/SearchAPI.cs | 2 +- Kyoo/Views/API/ShowsAPI.cs | 103 ++++++++++---- Kyoo/Views/API/SubtitleAPI.cs | 4 +- Kyoo/Views/API/TaskAPI.cs | 2 +- Kyoo/Views/API/ThumbnailAPI.cs | 2 +- Kyoo/Views/API/VideoAPI.cs | 2 +- Kyoo/Views/API/WatchAPI.cs | 2 +- 17 files changed, 247 insertions(+), 94 deletions(-) rename {Kyoo/Controllers => Kyoo.Common/Controllers/Implementations}/LibraryManager.cs (77%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 69302e21..6da3a7b7 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -33,7 +33,6 @@ namespace Kyoo.Controllers // Helpers - Task GetShowByPath(string path); Task AddShowLink(int showID, int? libraryID, int? collectionID); Task AddShowLink([NotNull] Show show, Library library, Collection collection); @@ -122,26 +121,26 @@ namespace Kyoo.Controllers Task> SearchPeople(string searchQuery); //Register values - Task RegisterLibrary(Library library); - Task RegisterCollection(Collection collection); - Task RegisterShow(Show show); - Task RegisterSeason(Season season); - Task RegisterEpisode(Episode episode); - Task RegisterTrack(Track track); - Task RegisterGenre(Genre genre); - Task RegisterStudio(Studio studio); - Task RegisterPeople(People people); + Task RegisterLibrary(Library library); + Task RegisterCollection(Collection collection); + Task RegisterShow(Show show); + Task RegisterSeason(Season season); + Task RegisterEpisode(Episode episode); + Task RegisterTrack(Track track); + Task RegisterGenre(Genre genre); + Task RegisterStudio(Studio studio); + Task RegisterPeople(People people); // Edit values - Task EditLibrary(Library library, bool resetOld); - Task EditCollection(Collection collection, bool resetOld); - Task EditShow(Show show, bool resetOld); - Task EditSeason(Season season, bool resetOld); - Task EditEpisode(Episode episode, bool resetOld); - Task EditTrack(Track track, bool resetOld); - Task EditGenre(Genre genre, bool resetOld); - Task EditStudio(Studio studio, bool resetOld); - Task EditPeople(People people, bool resetOld); + Task EditLibrary(Library library, bool resetOld); + Task EditCollection(Collection collection, bool resetOld); + Task EditShow(Show show, bool resetOld); + Task EditSeason(Season season, bool resetOld); + Task EditEpisode(Episode episode, bool resetOld); + Task EditTrack(Track track, bool resetOld); + Task EditGenre(Genre genre, bool resetOld); + Task EditStudio(Studio studio, bool resetOld); + Task EditPeople(People people, bool resetOld); // Delete values @@ -154,5 +153,27 @@ namespace Kyoo.Controllers Task DeleteGenre(Genre genre); Task DeleteStudio(Studio studio); Task DeletePeople(People people); + + //Delete by slug + Task DelteLibrary(string slug); + Task DeleteCollection(string slug); + Task DeleteShow(string slug); + Task DeleteSeason(string slug); + Task DeleteEpisode(string slug); + Task DeleteTrack(string slug); + Task DeleteGenre(string slug); + Task DeleteStudio(string slug); + Task DeletePeople(string slug); + + //Delete by id + Task DelteLibrary(int id); + Task DeleteCollection(int id); + Task DeleteShow(int id); + Task DeleteSeason(int id); + Task DeleteEpisode(int id); + Task DeleteTrack(int id); + Task DeleteGenre(int id); + Task DeleteStudio(int id); + Task DeletePeople(int id); } } diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 34daa52a..69a09df3 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -81,9 +81,9 @@ namespace Kyoo.Controllers Pagination page = default ) => GetAll(where, new Sort(sort), page); - Task Create([NotNull] T obj); - Task CreateIfNotExists([NotNull] T obj); - Task Edit([NotNull] T edited, bool resetOld); + Task Create([NotNull] T obj); + Task CreateIfNotExists([NotNull] T obj); + Task Edit([NotNull] T edited, bool resetOld); Task Delete(int id); Task Delete(string slug); @@ -99,7 +99,6 @@ namespace Kyoo.Controllers public interface IShowRepository : IRepository { - Task GetByPath(string path); Task AddShowLink(int showID, int? libraryID, int? collectionID); } diff --git a/Kyoo/Controllers/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs similarity index 77% rename from Kyoo/Controllers/LibraryManager.cs rename to Kyoo.Common/Controllers/Implementations/LibraryManager.cs index cf65dc8d..2ffbcaca 100644 --- a/Kyoo/Controllers/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -221,12 +221,7 @@ namespace Kyoo.Controllers { return _episodes.GetEpisodes(seasonID); } - - public Task GetShowByPath(string path) - { - return _shows.GetByPath(path); - } - + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return _shows.AddShowLink(showID, libraryID, collectionID); @@ -279,92 +274,92 @@ namespace Kyoo.Controllers return _people.Search(searchQuery); } - public Task RegisterLibrary(Library library) + public Task RegisterLibrary(Library library) { return _libraries.Create(library); } - public Task RegisterCollection(Collection collection) + public Task RegisterCollection(Collection collection) { return _collections.Create(collection); } - public Task RegisterShow(Show show) + public Task RegisterShow(Show show) { return _shows.Create(show); } - public Task RegisterSeason(Season season) + public Task RegisterSeason(Season season) { return _seasons.Create(season); } - public Task RegisterEpisode(Episode episode) + public Task RegisterEpisode(Episode episode) { return _episodes.Create(episode); } - public Task RegisterTrack(Track track) + public Task RegisterTrack(Track track) { return _tracks.Create(track); } - public Task RegisterGenre(Genre genre) + public Task RegisterGenre(Genre genre) { return _genres.Create(genre); } - public Task RegisterStudio(Studio studio) + public Task RegisterStudio(Studio studio) { return _studios.Create(studio); } - public Task RegisterPeople(People people) + public Task RegisterPeople(People people) { return _people.Create(people); } - public Task EditLibrary(Library library, bool resetOld) + public Task EditLibrary(Library library, bool resetOld) { return _libraries.Edit(library, resetOld); } - public Task EditCollection(Collection collection, bool resetOld) + public Task EditCollection(Collection collection, bool resetOld) { return _collections.Edit(collection, resetOld); } - public Task EditShow(Show show, bool resetOld) + public Task EditShow(Show show, bool resetOld) { return _shows.Edit(show, resetOld); } - public Task EditSeason(Season season, bool resetOld) + public Task EditSeason(Season season, bool resetOld) { return _seasons.Edit(season, resetOld); } - public Task EditEpisode(Episode episode, bool resetOld) + public Task EditEpisode(Episode episode, bool resetOld) { return _episodes.Edit(episode, resetOld); } - public Task EditTrack(Track track, bool resetOld) + public Task EditTrack(Track track, bool resetOld) { return _tracks.Edit(track, resetOld); } - public Task EditGenre(Genre genre, bool resetOld) + public Task EditGenre(Genre genre, bool resetOld) { return _genres.Edit(genre, resetOld); } - public Task EditStudio(Studio studio, bool resetOld) + public Task EditStudio(Studio studio, bool resetOld) { return _studios.Edit(studio, resetOld); } - public Task EditPeople(People people, bool resetOld) + public Task EditPeople(People people, bool resetOld) { return _people.Edit(people, resetOld); } @@ -413,5 +408,95 @@ namespace Kyoo.Controllers { return _people.Delete(people); } + + public Task DelteLibrary(string library) + { + return _libraries.Delete(library); + } + + public Task DeleteCollection(string collection) + { + return _collections.Delete(collection); + } + + public Task DeleteShow(string show) + { + return _shows.Delete(show); + } + + public Task DeleteSeason(string season) + { + return _seasons.Delete(season); + } + + public Task DeleteEpisode(string episode) + { + return _episodes.Delete(episode); + } + + public Task DeleteTrack(string track) + { + return _tracks.Delete(track); + } + + public Task DeleteGenre(string genre) + { + return _genres.Delete(genre); + } + + public Task DeleteStudio(string studio) + { + return _studios.Delete(studio); + } + + public Task DeletePeople(string people) + { + return _people.Delete(people); + } + + public Task DelteLibrary(int library) + { + return _libraries.Delete(library); + } + + public Task DeleteCollection(int collection) + { + return _collections.Delete(collection); + } + + public Task DeleteShow(int show) + { + return _shows.Delete(show); + } + + public Task DeleteSeason(int season) + { + return _seasons.Delete(season); + } + + public Task DeleteEpisode(int episode) + { + return _episodes.Delete(episode); + } + + public Task DeleteTrack(int track) + { + return _tracks.Delete(track); + } + + public Task DeleteGenre(int genre) + { + return _genres.Delete(genre); + } + + public Task DeleteStudio(int studio) + { + return _studios.Delete(studio); + } + + public Task DeletePeople(int people) + { + return _people.Delete(people); + } } } diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index e9253b07..955abc2d 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -72,7 +72,7 @@ namespace Kyoo.Controllers return await query.ToListAsync(); } - public async Task Create(Collection obj) + public async Task Create(Collection obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -91,17 +91,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Collection 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.ID; + return old; try { return await Create(obj); @@ -111,11 +111,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Collection edited, bool resetOld) + public async Task Edit(Collection edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -130,6 +130,7 @@ namespace Kyoo.Controllers Utility.Merge(old, edited); await _database.SaveChangesAsync(); + return old; } public async Task Delete(int id) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 4c3984b9..06a578dd 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using IdentityServer4.Services; -using Kyoo.Api; +using Kyoo.API; using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/Kyoo/Views/API/AccountAPI.cs b/Kyoo/Views/API/AccountAPI.cs index 80eea5f0..aa30b5d6 100644 --- a/Kyoo/Views/API/AccountAPI.cs +++ b/Kyoo/Views/API/AccountAPI.cs @@ -16,7 +16,7 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; -namespace Kyoo.Api +namespace Kyoo.API { public class RegisterRequest { diff --git a/Kyoo/Views/API/CollectionAPI.cs b/Kyoo/Views/API/CollectionAPI.cs index bd4658fa..535fcc84 100644 --- a/Kyoo/Views/API/CollectionAPI.cs +++ b/Kyoo/Views/API/CollectionAPI.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/EpisodesAPI.cs b/Kyoo/Views/API/EpisodesAPI.cs index abf0650d..fad605b1 100644 --- a/Kyoo/Views/API/EpisodesAPI.cs +++ b/Kyoo/Views/API/EpisodesAPI.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/LibrariesAPI.cs b/Kyoo/Views/API/LibrariesAPI.cs index eeeee468..4fe33cad 100644 --- a/Kyoo/Views/API/LibrariesAPI.cs +++ b/Kyoo/Views/API/LibrariesAPI.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/libraries")] [Route("api/library")] diff --git a/Kyoo/Views/API/PeopleAPI.cs b/Kyoo/Views/API/PeopleAPI.cs index ad27d8b9..59918418 100644 --- a/Kyoo/Views/API/PeopleAPI.cs +++ b/Kyoo/Views/API/PeopleAPI.cs @@ -5,7 +5,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/SearchAPI.cs b/Kyoo/Views/API/SearchAPI.cs index 4efcf93a..3f1489fe 100644 --- a/Kyoo/Views/API/SearchAPI.cs +++ b/Kyoo/Views/API/SearchAPI.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs index 58f9d504..76d2754d 100644 --- a/Kyoo/Views/API/ShowsAPI.cs +++ b/Kyoo/Views/API/ShowsAPI.cs @@ -5,34 +5,31 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/shows")] [Route("api/show")] [ApiController] public class ShowsAPI : ControllerBase { - private readonly ILibraryManager _libraryManager; + private readonly IShowRepository _shows; private readonly IProviderManager _providerManager; - private readonly DatabaseContext _database; private readonly IThumbnailsManager _thumbnailsManager; private readonly ITaskManager _taskManager; private readonly string _baseURL; - public ShowsAPI(ILibraryManager libraryManager, + public ShowsAPI(IShowRepository shows, IProviderManager providerManager, - DatabaseContext database, IThumbnailsManager thumbnailsManager, ITaskManager taskManager, IConfiguration configuration) { - _libraryManager = libraryManager; + _shows = shows; _providerManager = providerManager; - _database = database; _thumbnailsManager = thumbnailsManager; _taskManager = taskManager; _baseURL = configuration.GetValue("public_url").TrimEnd('/'); @@ -51,23 +48,34 @@ namespace Kyoo.Api if (limit == 0) limit = 20; - ICollection shows; try { - shows = await _libraryManager.GetShows(Utility.ParseWhere(where), + 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 }); } - - return new Page(shows, - x => $"{x.ID}", - _baseURL + Request.Path, - Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), - limit); + } + + [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}")] @@ -75,28 +83,67 @@ namespace Kyoo.Api [JsonDetailed] public async Task> GetShow(string slug) { - Show show = await _libraryManager.GetShow(slug); - + Show show = await _shows.Get(slug); if (show == null) return NotFound(); return show; } - - [HttpPost("edit/{slug}")] - [Authorize(Policy="Write")] - public async Task EditShow(string slug, [FromBody] Show show) - { - if (!ModelState.IsValid) - return BadRequest(show); - Show old = _database.Shows.AsNoTracking().FirstOrDefault(x => x.Slug == slug); + [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.Slug = slug; show.Path = old.Path; - await _libraryManager.EditShow(show, false); + 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(); } diff --git a/Kyoo/Views/API/SubtitleAPI.cs b/Kyoo/Views/API/SubtitleAPI.cs index 00d4d998..2861b40f 100644 --- a/Kyoo/Views/API/SubtitleAPI.cs +++ b/Kyoo/Views/API/SubtitleAPI.cs @@ -8,7 +8,7 @@ using Kyoo.Controllers; using Kyoo.Models.Watch; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("[controller]")] [ApiController] @@ -33,7 +33,7 @@ namespace Kyoo.Api string identifier, string extension) { - string languageTag = identifier.Length == 3 ? identifier.Substring(0, 3) : null; + string languageTag = identifier.Length >= 3 ? identifier.Substring(0, 3) : null; bool forced = identifier.Length > 4 && identifier.Substring(4) == "forced"; Track subtitle = null; diff --git a/Kyoo/Views/API/TaskAPI.cs b/Kyoo/Views/API/TaskAPI.cs index 24ab8845..4becbcd7 100644 --- a/Kyoo/Views/API/TaskAPI.cs +++ b/Kyoo/Views/API/TaskAPI.cs @@ -2,7 +2,7 @@ using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/ThumbnailAPI.cs b/Kyoo/Views/API/ThumbnailAPI.cs index 455c680d..3955cc6b 100644 --- a/Kyoo/Views/API/ThumbnailAPI.cs +++ b/Kyoo/Views/API/ThumbnailAPI.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { public class ThumbnailController : ControllerBase { diff --git a/Kyoo/Views/API/VideoAPI.cs b/Kyoo/Views/API/VideoAPI.cs index 2c12cea7..0570a03c 100644 --- a/Kyoo/Views/API/VideoAPI.cs +++ b/Kyoo/Views/API/VideoAPI.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.Api +namespace Kyoo.API { [Route("[controller]")] [ApiController] diff --git a/Kyoo/Views/API/WatchAPI.cs b/Kyoo/Views/API/WatchAPI.cs index 07039073..7e44997d 100644 --- a/Kyoo/Views/API/WatchAPI.cs +++ b/Kyoo/Views/API/WatchAPI.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.Api +namespace Kyoo.API { [Route("api/[controller]")] [ApiController] From b94328688b3bd5fc683db870b535dd74ea73d5cf Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 16 Jul 2020 21:45:32 +0200 Subject: [PATCH 12/36] Making repositories return whole objects --- .../Repositories/EpisodeRepository.cs | 15 ++++++------- .../Repositories/GenreRepository.cs | 13 ++++++------ .../Repositories/LibraryRepository.cs | 15 ++++++------- .../Repositories/PeopleRepository.cs | 15 ++++++------- .../Repositories/ProviderRepository.cs | 13 ++++++------ .../Repositories/SeasonRepository.cs | 15 ++++++------- .../Repositories/ShowRepository.cs | 21 ++++++++++--------- .../Repositories/StudioRepository.cs | 13 ++++++------ .../Repositories/TrackRepository.cs | 9 ++++---- 9 files changed, 69 insertions(+), 60 deletions(-) diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 90977937..eddb63bd 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -71,7 +71,7 @@ namespace Kyoo.Controllers return await _database.Episodes.ToListAsync(); } - public async Task Create(Episode obj) + public async Task Create(Episode obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -109,17 +109,17 @@ namespace Kyoo.Controllers * } */ - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Episode 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.ID; + return old; try { return await Create(obj); @@ -129,11 +129,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Episode edited, bool resetOld) + public async Task Edit(Episode edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -149,6 +149,7 @@ namespace Kyoo.Controllers await Validate(old); await _database.SaveChangesAsync(); + return old; } private async Task Validate(Episode obj) @@ -159,7 +160,7 @@ namespace Kyoo.Controllers if (obj.ExternalIDs != null) { foreach (MetadataID link in obj.ExternalIDs) - link.ProviderID = await _providers.CreateIfNotExists(link.Provider); + link.Provider = await _providers.CreateIfNotExists(link.Provider); } } diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index b6ee440d..bafa9226 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -54,7 +54,7 @@ namespace Kyoo.Controllers return await _database.Genres.ToListAsync(); } - public async Task Create(Genre obj) + public async Task Create(Genre obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -74,17 +74,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Genre obj) + public async Task CreateIfNotExists(Genre obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Genre old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -94,11 +94,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Genre edited, bool resetOld) + public async Task Edit(Genre edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -112,6 +112,7 @@ namespace Kyoo.Controllers Utility.Nullify(old); Utility.Merge(old, edited); await _database.SaveChangesAsync(); + return old; } public async Task Delete(int id) diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index acd20b43..4967fa0b 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -56,7 +56,7 @@ namespace Kyoo.Controllers return await _database.Libraries.ToListAsync(); } - public async Task Create(Library obj) + public async Task Create(Library obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -79,17 +79,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Library obj) + public async Task CreateIfNotExists(Library obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Library old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -99,11 +99,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Library edited, bool resetOld) + public async Task Edit(Library edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -118,13 +118,14 @@ namespace Kyoo.Controllers Utility.Merge(old, edited); await Validate(old); await _database.SaveChangesAsync(); + return old; } private async Task Validate(Library obj) { if (obj.ProviderLinks != null) foreach (ProviderLink link in obj.ProviderLinks) - link.ProviderID = await _providers.CreateIfNotExists(link.Provider); + link.Provider = await _providers.CreateIfNotExists(link.Provider); } public async Task Delete(int id) diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index df5f81c6..5dad3c4c 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -55,7 +55,7 @@ namespace Kyoo.Controllers return await _database.Peoples.ToListAsync(); } - public async Task Create(People obj) + public async Task Create(People obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -78,17 +78,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(People obj) + public async Task CreateIfNotExists(People obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); People old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -98,11 +98,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(People edited, bool resetOld) + public async Task Edit(People edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -117,13 +117,14 @@ namespace Kyoo.Controllers Utility.Merge(old, edited); await Validate(old); await _database.SaveChangesAsync(); + return old; } private async Task Validate(People obj) { if (obj.ExternalIDs != null) foreach (MetadataID link in obj.ExternalIDs) - link.ProviderID = await _providers.CreateIfNotExists(link.Provider); + link.Provider = await _providers.CreateIfNotExists(link.Provider); } public async Task Delete(int id) diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index 5b946139..ca82638e 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -54,7 +54,7 @@ namespace Kyoo.Controllers return await _database.Providers.ToListAsync(); } - public async Task Create(ProviderID obj) + public async Task Create(ProviderID obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -73,17 +73,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(ProviderID obj) + public async Task CreateIfNotExists(ProviderID obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); ProviderID old = await Get(obj.Name); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -93,11 +93,11 @@ namespace Kyoo.Controllers old = await Get(obj.Name); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(ProviderID edited, bool resetOld) + public async Task Edit(ProviderID edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -111,6 +111,7 @@ namespace Kyoo.Controllers Utility.Nullify(old); Utility.Merge(old, edited); await _database.SaveChangesAsync(); + return old; } public async Task Delete(int id) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index ef78914f..61ed44b2 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -70,7 +70,7 @@ namespace Kyoo.Controllers return await _database.Seasons.ToListAsync(); } - public async Task Create(Season obj) + public async Task Create(Season obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -93,17 +93,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Season obj) + public async Task CreateIfNotExists(Season obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Season old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -113,11 +113,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Season edited, bool resetOld) + public async Task Edit(Season edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -133,6 +133,7 @@ namespace Kyoo.Controllers await Validate(old); await _database.SaveChangesAsync(); + return old; } private async Task Validate(Season obj) @@ -143,7 +144,7 @@ namespace Kyoo.Controllers if (obj.ExternalIDs != null) { foreach (MetadataID link in obj.ExternalIDs) - link.ProviderID = await _providers.CreateIfNotExists(link.Provider); + link.Provider = await _providers.CreateIfNotExists(link.Provider); } } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 30b961f3..36ff0c39 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -98,7 +98,7 @@ namespace Kyoo.Controllers return await query.ToListAsync(); } - public async Task Create(Show obj) + public async Task Create(Show obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -127,17 +127,17 @@ namespace Kyoo.Controllers throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Show obj) + public async Task CreateIfNotExists(Show obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Show old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -147,11 +147,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Show edited, bool resetOld) + public async Task Edit(Show edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -166,24 +166,25 @@ namespace Kyoo.Controllers Utility.Merge(old, edited); await Validate(old); await _database.SaveChangesAsync(); + return old; } private async Task Validate(Show obj) { if (obj.Studio != null) - obj.StudioID = await _studios.CreateIfNotExists(obj.Studio); + obj.Studio = await _studios.CreateIfNotExists(obj.Studio); if (obj.GenreLinks != null) foreach (GenreLink link in obj.GenreLinks) - link.GenreID = await _genres.CreateIfNotExists(link.Genre); + link.Genre = await _genres.CreateIfNotExists(link.Genre); if (obj.People != null) foreach (PeopleLink link in obj.People) - link.PeopleID = await _people.CreateIfNotExists(link.People); + link.People = await _people.CreateIfNotExists(link.People); if (obj.ExternalIDs != null) foreach (MetadataID link in obj.ExternalIDs) - link.ProviderID = await _providers.CreateIfNotExists(link.Provider); + link.Provider = await _providers.CreateIfNotExists(link.Provider); } public async Task AddShowLink(int showID, int? libraryID, int? collectionID) diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 4e7583cb..dda47a2a 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -54,7 +54,7 @@ namespace Kyoo.Controllers return await _database.Studios.ToListAsync(); } - public async Task Create(Studio obj) + public async Task Create(Studio obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -72,17 +72,17 @@ namespace Kyoo.Controllers throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); throw; } - return obj.ID; + return obj; } - public async Task CreateIfNotExists(Studio obj) + public async Task CreateIfNotExists(Studio obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); Studio old = await Get(obj.Slug); if (old != null) - return old.ID; + return old; try { return await Create(obj); @@ -92,11 +92,11 @@ namespace Kyoo.Controllers old = await Get(obj.Slug); if (old == null) throw new SystemException("Unknown database state."); - return old.ID; + return old; } } - public async Task Edit(Studio edited, bool resetOld) + public async Task Edit(Studio edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -110,6 +110,7 @@ namespace Kyoo.Controllers Utility.Nullify(old); Utility.Merge(old, edited); await _database.SaveChangesAsync(); + return old; } public async Task Delete(int id) diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index c5731c0c..74fd4bf9 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -57,7 +57,7 @@ namespace Kyoo.Controllers return await _database.Tracks.ToListAsync(); } - public async Task Create(Track obj) + public async Task Create(Track obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -78,15 +78,15 @@ namespace Kyoo.Controllers throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); throw; } - return obj.ID; + return obj; } - public Task CreateIfNotExists(Track obj) + public Task CreateIfNotExists(Track obj) { return Create(obj); } - public async Task Edit(Track edited, bool resetOld) + public async Task Edit(Track edited, bool resetOld) { if (edited == null) throw new ArgumentNullException(nameof(edited)); @@ -100,6 +100,7 @@ namespace Kyoo.Controllers Utility.Nullify(old); Utility.Merge(old, edited); await _database.SaveChangesAsync(); + return old; } public async Task Delete(int id) From 2ad4c89806da4f3fd6901002ec54197fa3011dd7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 18 Jul 2020 19:33:37 +0200 Subject: [PATCH 13/36] Adding abstract class for repositories & CRUD APIs --- Kyoo.Common/Controllers/ILibraryManager.cs | 60 +++---- Kyoo.Common/Controllers/IRepository.cs | 10 +- .../Implementations/LibraryManager.cs | 4 +- Kyoo.Common/Models/Collection.cs | 2 +- Kyoo.Common/Models/Episode.cs | 2 +- Kyoo.Common/Models/Genre.cs | 2 +- Kyoo.Common/Models/IRessource.cs | 8 + Kyoo.Common/Models/Library.cs | 2 +- Kyoo.Common/Models/Page.cs | 6 +- Kyoo.Common/Models/People.cs | 2 +- Kyoo.Common/Models/ProviderID.cs | 3 +- Kyoo.Common/Models/Season.cs | 2 +- Kyoo.Common/Models/Show.cs | 2 +- Kyoo.Common/Models/Studio.cs | 2 +- Kyoo.Common/Models/Track.cs | 2 +- Kyoo.Common/Utility.cs | 72 -------- Kyoo.CommonAPI/ApiHelper.cs | 72 ++++++++ Kyoo.CommonAPI/CrudApi.cs | 153 +++++++++++++++++ .../API => Kyoo.CommonAPI}/JsonSerializer.cs | 0 Kyoo.CommonAPI/Kyoo.CommonAPI.csproj | 22 +++ Kyoo.CommonAPI/LocalRepository.cs | 156 +++++++++++++++++ Kyoo.sln | 6 + .../Repositories/CollectionRepository.cs | 129 ++------------ .../Repositories/EpisodeRepository.cs | 123 ++----------- .../Repositories/GenreRepository.cs | 102 +---------- Kyoo/Controllers/Repositories/Helper.cs | 14 -- .../Repositories/LibraryRepository.cs | 2 +- .../Repositories/PeopleRepository.cs | 2 +- .../Repositories/ProviderRepository.cs | 2 +- .../Repositories/SeasonRepository.cs | 2 +- .../Repositories/ShowRepository.cs | 13 +- .../Repositories/StudioRepository.cs | 2 +- .../Repositories/TrackRepository.cs | 2 +- Kyoo/Kyoo.csproj | 1 + Kyoo/Tasks/Crawler.cs | 3 +- Kyoo/Views/API/ShowsAPI.cs | 161 ------------------ 36 files changed, 518 insertions(+), 630 deletions(-) create mode 100644 Kyoo.Common/Models/IRessource.cs create mode 100644 Kyoo.CommonAPI/ApiHelper.cs create mode 100644 Kyoo.CommonAPI/CrudApi.cs rename {Kyoo/Views/API => Kyoo.CommonAPI}/JsonSerializer.cs (100%) create mode 100644 Kyoo.CommonAPI/Kyoo.CommonAPI.csproj create mode 100644 Kyoo.CommonAPI/LocalRepository.cs delete mode 100644 Kyoo/Controllers/Repositories/Helper.cs 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(); - } } } From 81f555ca7e0d4964fc45e285b38b5129d4b3ff0f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 19 Jul 2020 00:15:34 +0200 Subject: [PATCH 14/36] Finishing the repositories's rework --- Kyoo.Common/Models/Track.cs | 7 +- .../Repositories/CollectionRepository.cs | 2 +- .../Repositories/EpisodeRepository.cs | 4 +- .../Repositories/GenreRepository.cs | 6 +- .../Repositories/LibraryRepository.cs | 112 ++----------- .../Repositories/PeopleRepository.cs | 114 ++------------ .../Repositories/ProviderRepository.cs | 109 ++----------- .../Repositories/SeasonRepository.cs | 128 +++------------ .../Repositories/ShowRepository.cs | 149 +++--------------- .../Repositories/StudioRepository.cs | 111 ++----------- .../Repositories/TrackRepository.cs | 110 ++++--------- 11 files changed, 144 insertions(+), 708 deletions(-) diff --git a/Kyoo.Common/Models/Track.cs b/Kyoo.Common/Models/Track.cs index a0688c32..61209807 100644 --- a/Kyoo.Common/Models/Track.cs +++ b/Kyoo.Common/Models/Track.cs @@ -93,9 +93,10 @@ namespace Kyoo.Models { if (Type != StreamType.Subtitle) return null; - string slug = $"/subtitle/{Episode.Slug}.{Language ?? ID.ToString()}"; - if (IsForced) - slug += "-forced"; + + string slug = string.IsNullOrEmpty(Language) + ? ID.ToString() + : $"{Episode.Slug}.{Language}{(IsForced ? "-forced" : "")}"; switch (Codec) { case "ass": diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index a4966407..72605d39 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -22,7 +22,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Collections - .Where(x => EF.Functions.Like(x.Name, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 6f9fd900..8c83be3a 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -38,7 +38,7 @@ namespace Kyoo.Controllers public override Task Get(string slug) { - Match match = Regex.Match(slug, @"(.*)-s(\d*)-e(\d*)"); + Match match = Regex.Match(slug, @"(?.*)-s(?\d*)-e(?\d*)"); if (!match.Success) return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug); @@ -57,7 +57,7 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { return await _database.Episodes - .Where(x => EF.Functions.Like(x.Title, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index 9b2abcb1..dc891283 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -21,10 +21,10 @@ namespace Kyoo.Controllers } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Genres - .Where(genre => EF.Functions.Like(genre.Name, $"%{query}%")) + .Where(genre => EF.Functions.ILike(genre.Name, $"%{query}%")) .Take(20) .ToListAsync(); } @@ -57,7 +57,7 @@ namespace Kyoo.Controllers return Task.CompletedTask; } - public async Task Delete(Genre obj) + public override async Task Delete(Genre obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 4ae6757a..7766fe8e 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -9,54 +9,41 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class LibraryRepository : ILibraryRepository + public class LibraryRepository : LocalRepository, ILibraryRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + protected override Expression> DefaultSort => x => x.ID; - public LibraryRepository(DatabaseContext database, IProviderRepository providers) + public LibraryRepository(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(); + await _database.DisposeAsync(); + await _providers.DisposeAsync(); } - public Task Get(int id) - { - return _database.Libraries.FirstOrDefaultAsync(x => x.ID == id); - } - - public Task Get(string slug) - { - return _database.Libraries.FirstOrDefaultAsync(x => x.Slug == slug); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Libraries - .Where(x => EF.Functions.Like(x.Name, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Libraries.ToListAsync(); - } - - public async Task Create(Library obj) + public override async Task Create(Library obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -74,73 +61,22 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); throw; } return obj; } - - public async Task CreateIfNotExists(Library obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Library 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(Library edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - Library old = await Get(edited.Name); - - if (old == null) - throw new ItemNotFound($"No library found with the name {edited.Name}."); - - if (resetOld) - Utility.Nullify(old); - Utility.Merge(old, edited); - await Validate(old); - await _database.SaveChangesAsync(); - return old; - } - - private async Task Validate(Library obj) + protected override async Task Validate(Library obj) { if (obj.ProviderLinks != null) foreach (ProviderLink link in obj.ProviderLinks) link.Provider = await _providers.CreateIfNotExists(link.Provider); } - public async Task Delete(int id) - { - Library obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - Library obj = await Get(slug); - await Delete(obj); - } - - public async Task Delete(Library obj) + public override async Task Delete(Library obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -154,23 +90,5 @@ namespace Kyoo.Controllers _database.Entry(entry).State = EntityState.Deleted; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Library 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/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 7798cd8b..77b5f356 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -9,53 +9,40 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class PeopleRepository : IPeopleRepository + public class PeopleRepository : LocalRepository, IPeopleRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + protected override Expression> DefaultSort => x => x.Name; - public PeopleRepository(DatabaseContext database, IProviderRepository providers) + public PeopleRepository(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(); + await _database.DisposeAsync(); + await _providers.DisposeAsync(); } - public Task Get(int id) - { - return _database.Peoples.FirstOrDefaultAsync(x => x.ID == id); - } - - public Task Get(string slug) - { - return _database.Peoples.FirstOrDefaultAsync(x => x.Slug == slug); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Peoples - .Where(people => EF.Functions.Like(people.Name, $"%{query}%")) + .Where(people => EF.Functions.ILike(people.Name, $"%{query}%")) .Take(20) .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Peoples.ToListAsync(); - } - - public async Task Create(People obj) + public override async Task Create(People obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -73,7 +60,7 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); throw; } @@ -81,65 +68,14 @@ namespace Kyoo.Controllers return obj; } - public async Task CreateIfNotExists(People obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - People 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(People edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - People old = await Get(edited.Slug); - - if (old == null) - throw new ItemNotFound($"No people 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(People obj) + protected override async Task Validate(People obj) { if (obj.ExternalIDs != null) foreach (MetadataID link in obj.ExternalIDs) link.Provider = await _providers.CreateIfNotExists(link.Provider); } - public async Task Delete(int id) - { - People obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - People obj = await Get(slug); - await Delete(obj); - } - - public async Task Delete(People obj) + public override async Task Delete(People obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -153,23 +89,5 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (People 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/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index be8f3989..f1477578 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -9,52 +9,26 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class ProviderRepository : IProviderRepository + public class ProviderRepository : LocalRepository, IProviderRepository { private readonly DatabaseContext _database; + protected override Expression> DefaultSort => x => x.Slug; - public ProviderRepository(DatabaseContext database) + public ProviderRepository(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.Providers.FirstOrDefaultAsync(x => x.ID == id); - } - - public async Task Get(string slug) - { - return await _database.Providers.FirstOrDefaultAsync(x => x.Name == slug); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Providers - .Where(x => EF.Functions.Like(x.Name, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Providers.ToListAsync(); - } - - public async Task Create(ProviderID obj) + public override async Task Create(ProviderID obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -68,7 +42,7 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated provider (name {obj.Name} already exists)."); throw; } @@ -76,57 +50,12 @@ namespace Kyoo.Controllers return obj; } - public async Task CreateIfNotExists(ProviderID obj) + protected override Task Validate(ProviderID ressource) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - ProviderID old = await Get(obj.Name); - if (old != null) - return old; - try - { - return await Create(obj); - } - catch (DuplicatedItemException) - { - old = await Get(obj.Name); - if (old == null) - throw new SystemException("Unknown database state."); - return old; - } + return Task.CompletedTask; } - public async Task Edit(ProviderID edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - ProviderID old = await Get(edited.Name); - - if (old == null) - throw new ItemNotFound($"No provider found with the name {edited.Name}."); - - if (resetOld) - Utility.Nullify(old); - Utility.Merge(old, edited); - await _database.SaveChangesAsync(); - return old; - } - - public async Task Delete(int id) - { - ProviderID obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - ProviderID obj = await Get(slug); - await Delete(obj); - } - - public async Task Delete(ProviderID obj) + public override async Task Delete(ProviderID obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -135,23 +64,5 @@ namespace Kyoo.Controllers // TODO handle ExternalID deletion when they refer to this providerID. await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (ProviderID 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/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 045599b8..8ecb92ad 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -9,44 +10,44 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class SeasonRepository : ISeasonRepository + public class SeasonRepository : LocalRepository, ISeasonRepository { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; private readonly IEpisodeRepository _episodes; + protected override Expression> DefaultSort => x => x.SeasonNumber; public SeasonRepository(DatabaseContext database, IProviderRepository providers, IEpisodeRepository episodes) + : base(database) { _database = database; _providers = providers; _episodes = episodes; } - - public void Dispose() + + + public override void Dispose() { _database.Dispose(); + _providers.Dispose(); + _episodes.Dispose(); } - public ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { - return _database.DisposeAsync(); - } - - public Task Get(int id) - { - return _database.Seasons.FirstOrDefaultAsync(x => x.ID == id); + await _database.DisposeAsync(); + await _providers.DisposeAsync(); + await _episodes.DisposeAsync(); } - public Task Get(string slug) + public override Task Get(string slug) { - int index = slug.IndexOf("-s", StringComparison.Ordinal); - if (index == -1) - throw new InvalidOperationException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); - string showSlug = slug.Substring(0, index); - if (!int.TryParse(slug.Substring(index + 2), out int seasonNumber)) - throw new InvalidOperationException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); - return Get(showSlug, seasonNumber); + Match match = Regex.Match(slug, @"(?.*)-s(?\d*)"); + + if (!match.Success) + throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); + return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value)); } public Task Get(string showSlug, int seasonNumber) @@ -55,22 +56,15 @@ namespace Kyoo.Controllers && x.SeasonNumber == seasonNumber); } - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Seasons - .Where(x => EF.Functions.Like(x.Title, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) .Take(20) .ToListAsync(); } - - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Seasons.ToListAsync(); - } - - public async Task Create(Season obj) + + public override async Task Create(Season obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -88,55 +82,15 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); throw; } return obj; } - - public async Task CreateIfNotExists(Season obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - Season 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(Season edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - Season old = await Get(edited.Slug); - - if (old == null) - throw new ItemNotFound($"No season 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(Season obj) + protected override async Task Validate(Season obj) { if (obj.ShowID <= 0) throw new InvalidOperationException($"Can't store a season not related to any show (showID: {obj.ShowID})."); @@ -158,25 +112,13 @@ namespace Kyoo.Controllers return await _database.Seasons.Where(x => x.Show.Slug == showSlug).ToListAsync(); } - public async Task Delete(int id) - { - Season obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - Season obj = await Get(slug); - await Delete(obj); - } - public async Task Delete(string showSlug, int seasonNumber) { Season obj = await Get(showSlug, seasonNumber); await Delete(obj); } - public async Task Delete(Season obj) + public override async Task Delete(Season obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -192,23 +134,5 @@ namespace Kyoo.Controllers if (obj.Episodes != null) await _episodes.DeleteRange(obj.Episodes); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Season 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/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index db1b7b83..b0335786 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -3,14 +3,13 @@ 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; namespace Kyoo.Controllers { - public class ShowRepository : IShowRepository + public class ShowRepository : LocalRepository, IShowRepository { private readonly DatabaseContext _database; private readonly IStudioRepository _studios; @@ -19,6 +18,7 @@ namespace Kyoo.Controllers private readonly IProviderRepository _providers; private readonly ISeasonRepository _seasons; private readonly IEpisodeRepository _episodes; + protected override Expression> DefaultSort => x => x.Title; public ShowRepository(DatabaseContext database, IStudioRepository studios, @@ -27,6 +27,7 @@ namespace Kyoo.Controllers IProviderRepository providers, ISeasonRepository seasons, IEpisodeRepository episodes) + : base(database) { _database = database; _studios = studios; @@ -36,70 +37,39 @@ namespace Kyoo.Controllers _seasons = seasons; _episodes = episodes; } - - public void Dispose() + + public override void Dispose() { _database.Dispose(); _studios.Dispose(); + _people.Dispose(); + _genres.Dispose(); + _providers.Dispose(); + _seasons.Dispose(); + _episodes.Dispose(); } - public async ValueTask DisposeAsync() + public override async ValueTask DisposeAsync() { - await Task.WhenAll(_database.DisposeAsync().AsTask(), _studios.DisposeAsync().AsTask()); - } - - public Task Get(int id) - { - return _database.Shows.FirstOrDefaultAsync(x => x.ID == id); - } - - public Task Get(string slug) - { - return _database.Shows.FirstOrDefaultAsync(x => x.Slug == slug); + await _database.DisposeAsync(); + await _studios.DisposeAsync(); + await _people.DisposeAsync(); + await _genres.DisposeAsync(); + await _providers.DisposeAsync(); + await _seasons.DisposeAsync(); + await _episodes.DisposeAsync(); } - public Task GetByPath(string path) - { - return _database.Shows.FirstOrDefaultAsync(x => x.Path == path); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Shows - .FromSqlInterpolated($@"SELECT * FROM Shows WHERE 'Shows.Title' LIKE {$"%{query}%"} - OR 'Shows.Aliases' LIKE {$"%{query}%"}") + .Where(x => EF.Functions.ILike(x.Title, $"%{query}%") + /*|| EF.Functions.ILike(x.Aliases, $"%{query}%")*/) .Take(20) .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - IQueryable query = _database.Shows; - - if (where != null) - query = query.Where(where); - - Expression> sortKey = sort.Key ?? (x => x.Title); - query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); - - if (limit.AfterID != 0) - { - Show 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 async Task Create(Show obj) + public override async Task Create(Show obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -123,7 +93,7 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); throw; } @@ -131,46 +101,7 @@ namespace Kyoo.Controllers return obj; } - public async Task CreateIfNotExists(Show obj) - { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - Show 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(Show edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - Show old = await Get(edited.Slug); - - if (old == null) - throw new ItemNotFound($"No show 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(Show obj) + protected override async Task Validate(Show obj) { if (obj.Studio != null) obj.Studio = await _studios.CreateIfNotExists(obj.Studio); @@ -210,20 +141,8 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync(); } - - public async Task Delete(int id) - { - Show obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - Show obj = await Get(slug); - await Delete(obj); - } - public async Task Delete(Show obj) + public override async Task Delete(Show obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -258,23 +177,5 @@ namespace Kyoo.Controllers if (obj.Episodes != null) await _episodes.DeleteRange(obj.Episodes); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Show 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/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 3dc06439..299eb712 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -9,52 +9,26 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class StudioRepository : IStudioRepository + public class StudioRepository : LocalRepository, IStudioRepository { private readonly DatabaseContext _database; + protected override Expression> DefaultSort => x => x.Name; - public StudioRepository(DatabaseContext database) + public StudioRepository(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.Studios.FirstOrDefaultAsync(x => x.ID == id); - } - - public async Task Get(string slug) - { - return await _database.Studios.FirstOrDefaultAsync(x => x.Slug == slug); - } - - public async Task> Search(string query) + public override async Task> Search(string query) { return await _database.Studios - .Where(x => EF.Functions.Like(x.Name, $"%{query}%")) + .Where(x => EF.Functions.ILike(x.Name, $"%{query}%")) .Take(20) .ToListAsync(); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Studios.ToListAsync(); - } - - public async Task Create(Studio obj) + public override async Task Create(Studio obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -68,64 +42,19 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); throw; } return obj; } - - public async Task CreateIfNotExists(Studio obj) + + protected override Task Validate(Studio ressource) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - Studio 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(Studio edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - Studio old = await Get(edited.Name); - - if (old == null) - throw new ItemNotFound($"No studio found with the name {edited.Name}."); - - if (resetOld) - Utility.Nullify(old); - Utility.Merge(old, edited); - await _database.SaveChangesAsync(); - return old; - } - - public async Task Delete(int id) - { - Studio obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - Studio obj = await Get(slug); - await Delete(obj); + return Task.CompletedTask; } - public async Task Delete(Studio obj) + public override async Task Delete(Studio obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -137,23 +66,5 @@ namespace Kyoo.Controllers show.StudioID = null; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Studio 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/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 5362c63d..01334f2f 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; @@ -8,34 +9,39 @@ using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers { - public class TrackRepository : ITrackRepository + public class TrackRepository : LocalRepository, ITrackRepository { private readonly DatabaseContext _database; + protected override Expression> DefaultSort => x => x.ID; - public TrackRepository(DatabaseContext database) + public TrackRepository(DatabaseContext database) : base(database) { _database = database; } - - public void Dispose() - { - _database.Dispose(); - } - public ValueTask DisposeAsync() + public override Task Get(string slug) { - return _database.DisposeAsync(); - } - - public async Task Get(int id) - { - return await _database.Tracks.FirstOrDefaultAsync(x => x.ID == id); - } - - public Task Get(string slug) - { - throw new InvalidOperationException("Tracks do not support the get by slug method."); + Match match = Regex.Match(slug, + @"(?.*)-s(?\d*)-e(?\d*).(?.{0,3})(?-forced)?(\..*)?"); + + if (!match.Success) + { + if (int.TryParse(slug, out int id)) + return Get(id); + throw new ArgumentException("Invalid track slug. Format: {episodeSlug}.{language}[-forced][.{extension}]"); + } + + string showSlug = match.Groups["show"].Value; + int seasonNumber = int.Parse(match.Groups["season"].Value); + int episodeNumber = int.Parse(match.Groups["episode"].Value); + string language = match.Groups["language"].Value; + bool forced = match.Groups["forced"].Success; + return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber + && x.Language == language + && x.IsForced == forced); } public Task Get(int episodeID, string languageTag, bool isForced) @@ -45,19 +51,12 @@ namespace Kyoo.Controllers && x.IsForced == isForced); } - public Task> Search(string query) + public override Task> Search(string query) { throw new InvalidOperationException("Tracks do not support the search method."); } - public async Task> GetAll(Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - return await _database.Tracks.ToListAsync(); - } - - public async Task Create(Track obj) + public override async Task Create(Track obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -74,48 +73,19 @@ namespace Kyoo.Controllers catch (DbUpdateException ex) { _database.DiscardChanges(); - if (Helper.IsDuplicateException(ex)) + if (IsDuplicateException(ex)) throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); throw; } return obj; } - public Task CreateIfNotExists(Track obj) + protected override Task Validate(Track ressource) { - return Create(obj); - } - - public async Task Edit(Track edited, bool resetOld) - { - if (edited == null) - throw new ArgumentNullException(nameof(edited)); - - Track old = await Get(edited.ID); - - if (old == null) - throw new ItemNotFound($"No track found with the ID {edited.ID}."); - - if (resetOld) - Utility.Nullify(old); - Utility.Merge(old, edited); - await _database.SaveChangesAsync(); - return old; - } - - public async Task Delete(int id) - { - Track obj = await Get(id); - await Delete(obj); - } - - public async Task Delete(string slug) - { - Track obj = await Get(slug); - await Delete(obj); + return Task.CompletedTask; } - public async Task Delete(Track obj) + public override async Task Delete(Track obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); @@ -123,23 +93,5 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } - - public async Task DeleteRange(IEnumerable objs) - { - foreach (Track 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 From 3e7ddff99e888329a2a5c8f619dad985850694e4 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 20 Jul 2020 04:11:01 +0200 Subject: [PATCH 15/36] Completing the basic show api --- Kyoo.Common/Controllers/ILibraryManager.cs | 32 ++- Kyoo.Common/Controllers/IRepository.cs | 22 +- .../Implementations/LibraryManager.cs | 270 +++++++++--------- .../Models/{ => Ressources}/Collection.cs | 0 .../Models/{ => Ressources}/Episode.cs | 0 Kyoo.Common/Models/{ => Ressources}/Genre.cs | 0 .../Models/{ => Ressources}/IRessource.cs | 0 .../Models/{ => Ressources}/Library.cs | 0 Kyoo.Common/Models/{ => Ressources}/People.cs | 0 .../Models/{ => Ressources}/ProviderID.cs | 0 Kyoo.Common/Models/{ => Ressources}/Season.cs | 0 Kyoo.Common/Models/{ => Ressources}/Show.cs | 0 Kyoo.Common/Models/{ => Ressources}/Studio.cs | 0 Kyoo.Common/Models/{ => Ressources}/Track.cs | 0 Kyoo.CommonAPI/CrudApi.cs | 37 ++- Kyoo.CommonAPI/Kyoo.CommonAPI.csproj | 1 + Kyoo.CommonAPI/LocalRepository.cs | 12 +- .../Repositories/SeasonRepository.cs | 20 +- Kyoo/Startup.cs | 2 +- Kyoo/Views/API/AccountAPI.cs | 2 +- Kyoo/Views/API/CollectionAPI.cs | 31 +- Kyoo/Views/API/EpisodesAPI.cs | 2 +- Kyoo/Views/API/GenresAPI.cs | 2 +- Kyoo/Views/API/LibrariesAPI.cs | 2 +- Kyoo/Views/API/PeopleAPI.cs | 2 +- Kyoo/Views/API/ProviderAPI.cs | 2 +- Kyoo/Views/API/SearchAPI.cs | 2 +- Kyoo/Views/API/ShowsAPI.cs | 21 -- Kyoo/Views/API/ShowsApi.cs | 85 ++++++ Kyoo/Views/API/StudioAPI.cs | 2 +- Kyoo/Views/API/SubtitleAPI.cs | 2 +- Kyoo/Views/API/TaskAPI.cs | 2 +- Kyoo/Views/API/ThumbnailAPI.cs | 2 +- Kyoo/Views/API/VideoAPI.cs | 2 +- Kyoo/Views/API/WatchAPI.cs | 2 +- 35 files changed, 351 insertions(+), 208 deletions(-) rename Kyoo.Common/Models/{ => Ressources}/Collection.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Episode.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Genre.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/IRessource.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Library.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/People.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/ProviderID.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Season.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Show.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Studio.cs (100%) rename Kyoo.Common/Models/{ => Ressources}/Track.cs (100%) delete mode 100644 Kyoo/Views/API/ShowsAPI.cs create mode 100644 Kyoo/Views/API/ShowsApi.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 232c1cdb..33d65c42 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -10,6 +10,18 @@ namespace Kyoo.Controllers { public interface ILibraryManager : IDisposable, IAsyncDisposable { + // Repositories + ILibraryRepository LibraryRepository { get; } + ICollectionRepository CollectionRepository { get; } + IShowRepository ShowRepository { get; } + ISeasonRepository SeasonRepository { get; } + IEpisodeRepository EpisodeRepository { get; } + ITrackRepository TrackRepository { get; } + IPeopleRepository PeopleRepository { get; } + IStudioRepository StudioRepository { get; } + IGenreRepository GenreRepository { get; } + IProviderRepository ProviderRepository { get; } + // Get by slug Task GetLibrary(string slug); Task GetCollection(string slug); @@ -24,8 +36,24 @@ namespace Kyoo.Controllers Task GetPeople(string slug); // Get by relations - Task> GetSeasons(int showID); - Task> GetSeasons(string showSlug); + Task> GetSeasons(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetSeasons(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetSeasons(showID, where, new Sort(sort), limit); + Task> GetSeasons(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetSeasons(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetSeasons(showSlug, where, new Sort(sort), limit); Task> GetEpisodes(int showID, int seasonNumber); Task> GetEpisodes(string showSlug, int seasonNumber); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 54bcb678..2c971a13 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -109,8 +109,26 @@ namespace Kyoo.Controllers Task Get(string showSlug, int seasonNumber); Task Delete(string showSlug, int seasonNumber); - Task> GetSeasons(int showID); - Task> GetSeasons(string showSlug); + Task> GetSeasons(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetSeasons(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetSeasons(showID, where, new Sort(sort), limit); + + Task> GetSeasons(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetSeasons(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetSeasons(showSlug, where, new Sort(sort), limit); + } public interface IEpisodeRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index cd3f080d..6956100c 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -8,223 +8,229 @@ namespace Kyoo.Controllers { public class LibraryManager : ILibraryManager { - private readonly ILibraryRepository _libraries; - private readonly ICollectionRepository _collections; - private readonly IShowRepository _shows; - private readonly ISeasonRepository _seasons; - private readonly IEpisodeRepository _episodes; - private readonly ITrackRepository _tracks; - private readonly IGenreRepository _genres; - private readonly IStudioRepository _studios; - private readonly IPeopleRepository _people; - private readonly IProviderRepository _providers; + public ILibraryRepository LibraryRepository { get; } + public ICollectionRepository CollectionRepository { get; } + public IShowRepository ShowRepository { get; } + public ISeasonRepository SeasonRepository { get; } + public IEpisodeRepository EpisodeRepository { get; } + public ITrackRepository TrackRepository { get; } + public IGenreRepository GenreRepository { get; } + public IStudioRepository StudioRepository { get; } + public IPeopleRepository PeopleRepository { get; } + public IProviderRepository ProviderRepository { get; } - public LibraryManager(ILibraryRepository libraries, - ICollectionRepository collections, - IShowRepository shows, - ISeasonRepository seasons, - IEpisodeRepository episodes, - ITrackRepository tracks, - IGenreRepository genres, - IStudioRepository studios, - IProviderRepository providers, - IPeopleRepository people) + public LibraryManager(ILibraryRepository libraryRepository, + ICollectionRepository collectionRepository, + IShowRepository showRepository, + ISeasonRepository seasonRepository, + IEpisodeRepository episodeRepository, + ITrackRepository trackRepository, + IGenreRepository genreRepository, + IStudioRepository studioRepository, + IProviderRepository providerRepository, + IPeopleRepository peopleRepository) { - _libraries = libraries; - _collections = collections; - _shows = shows; - _seasons = seasons; - _episodes = episodes; - _tracks = tracks; - _genres = genres; - _studios = studios; - _providers = providers; - _people = people; + LibraryRepository = libraryRepository; + CollectionRepository = collectionRepository; + ShowRepository = showRepository; + SeasonRepository = seasonRepository; + EpisodeRepository = episodeRepository; + TrackRepository = trackRepository; + GenreRepository = genreRepository; + StudioRepository = studioRepository; + ProviderRepository = providerRepository; + PeopleRepository = peopleRepository; } public void Dispose() { - _libraries.Dispose(); - _collections.Dispose(); - _shows.Dispose(); - _seasons.Dispose(); - _episodes.Dispose(); - _tracks.Dispose(); - _genres.Dispose(); - _studios.Dispose(); - _people.Dispose(); - _providers.Dispose(); + LibraryRepository.Dispose(); + CollectionRepository.Dispose(); + ShowRepository.Dispose(); + SeasonRepository.Dispose(); + EpisodeRepository.Dispose(); + TrackRepository.Dispose(); + GenreRepository.Dispose(); + StudioRepository.Dispose(); + PeopleRepository.Dispose(); + ProviderRepository.Dispose(); } public async ValueTask DisposeAsync() { await Task.WhenAll( - _libraries.DisposeAsync().AsTask(), - _collections.DisposeAsync().AsTask(), - _shows.DisposeAsync().AsTask(), - _seasons.DisposeAsync().AsTask(), - _episodes.DisposeAsync().AsTask(), - _tracks.DisposeAsync().AsTask(), - _genres.DisposeAsync().AsTask(), - _studios.DisposeAsync().AsTask(), - _people.DisposeAsync().AsTask(), - _providers.DisposeAsync().AsTask() + LibraryRepository.DisposeAsync().AsTask(), + CollectionRepository.DisposeAsync().AsTask(), + ShowRepository.DisposeAsync().AsTask(), + SeasonRepository.DisposeAsync().AsTask(), + EpisodeRepository.DisposeAsync().AsTask(), + TrackRepository.DisposeAsync().AsTask(), + GenreRepository.DisposeAsync().AsTask(), + StudioRepository.DisposeAsync().AsTask(), + PeopleRepository.DisposeAsync().AsTask(), + ProviderRepository.DisposeAsync().AsTask() ); } public Task GetLibrary(string slug) { - return _libraries.Get(slug); + return LibraryRepository.Get(slug); } public Task GetCollection(string slug) { - return _collections.Get(slug); + return CollectionRepository.Get(slug); } public Task GetShow(string slug) { - return _shows.Get(slug); + return ShowRepository.Get(slug); } public Task GetSeason(string showSlug, int seasonNumber) { - return _seasons.Get(showSlug, seasonNumber); + return SeasonRepository.Get(showSlug, seasonNumber); } public Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber) { - return _episodes.Get(showSlug, seasonNumber, episodeNumber); + return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber); } public Task GetMovieEpisode(string movieSlug) { - return _episodes.Get(movieSlug); + return EpisodeRepository.Get(movieSlug); } public Task GetTrack(int id) { - return _tracks.Get(id); + return TrackRepository.Get(id); } public Task GetTrack(int episodeID, string language, bool isForced) { - return _tracks.Get(episodeID, language, isForced); + return TrackRepository.Get(episodeID, language, isForced); } public Task GetGenre(string slug) { - return _genres.Get(slug); + return GenreRepository.Get(slug); } public Task GetStudio(string slug) { - return _studios.Get(slug); + return StudioRepository.Get(slug); } public Task GetPeople(string slug) { - return _people.Get(slug); + return PeopleRepository.Get(slug); } public Task> GetLibraries(Expression> where = null, Sort sort = default, Pagination page = default) { - return _libraries.GetAll(where, sort, page); + return LibraryRepository.GetAll(where, sort, page); } public Task> GetCollections(Expression> where = null, Sort sort = default, Pagination page = default) { - return _collections.GetAll(where, sort, page); + return CollectionRepository.GetAll(where, sort, page); } public Task> GetShows(Expression> where = null, Sort sort = default, Pagination limit = default) { - return _shows.GetAll(where, sort, limit); + return ShowRepository.GetAll(where, sort, limit); } public Task> GetSeasons(Expression> where = null, Sort sort = default, Pagination page = default) { - return _seasons.GetAll(where, sort, page); + return SeasonRepository.GetAll(where, sort, page); } public Task> GetEpisodes(Expression> where = null, Sort sort = default, Pagination page = default) { - return _episodes.GetAll(where, sort, page); + return EpisodeRepository.GetAll(where, sort, page); } public Task> GetTracks(Expression> where = null, Sort sort = default, Pagination page = default) { - return _tracks.GetAll(where, sort, page); + return TrackRepository.GetAll(where, sort, page); } public Task> GetStudios(Expression> where = null, Sort sort = default, Pagination page = default) { - return _studios.GetAll(where, sort, page); + return StudioRepository.GetAll(where, sort, page); } public Task> GetPeople(Expression> where = null, Sort sort = default, Pagination page = default) { - return _people.GetAll(where, sort, page); + return PeopleRepository.GetAll(where, sort, page); } public Task> GetGenres(Expression> where = null, Sort sort = default, Pagination page = default) { - return _genres.GetAll(where, sort, page); + return GenreRepository.GetAll(where, sort, page); } public Task> GetProviders(Expression> where = null, Sort sort = default, Pagination page = default) { - return _providers.GetAll(where, sort, page); + return ProviderRepository.GetAll(where, sort, page); } - public Task> GetSeasons(int showID) + public Task> GetSeasons(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return _seasons.GetSeasons(showID); + return SeasonRepository.GetSeasons(showID, where, sort, limit); } - public Task> GetSeasons(string showSlug) + public Task> GetSeasons(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return _seasons.GetSeasons(showSlug); + return SeasonRepository.GetSeasons(showSlug, where, sort, limit); } public Task> GetEpisodes(int showID, int seasonNumber) { - return _episodes.GetEpisodes(showID, seasonNumber); + return EpisodeRepository.GetEpisodes(showID, seasonNumber); } public Task> GetEpisodes(string showSlug, int seasonNumber) { - return _episodes.GetEpisodes(showSlug, seasonNumber); + return EpisodeRepository.GetEpisodes(showSlug, seasonNumber); } public Task> GetEpisodes(int seasonID) { - return _episodes.GetEpisodes(seasonID); + return EpisodeRepository.GetEpisodes(seasonID); } public Task AddShowLink(int showID, int? libraryID, int? collectionID) { - return _shows.AddShowLink(showID, libraryID, collectionID); + return ShowRepository.AddShowLink(showID, libraryID, collectionID); } public Task AddShowLink(Show show, Library library, Collection collection) @@ -236,267 +242,267 @@ namespace Kyoo.Controllers public Task> SearchLibraries(string searchQuery) { - return _libraries.Search(searchQuery); + return LibraryRepository.Search(searchQuery); } public Task> SearchCollections(string searchQuery) { - return _collections.Search(searchQuery); + return CollectionRepository.Search(searchQuery); } public Task> SearchShows(string searchQuery) { - return _shows.Search(searchQuery); + return ShowRepository.Search(searchQuery); } public Task> SearchSeasons(string searchQuery) { - return _seasons.Search(searchQuery); + return SeasonRepository.Search(searchQuery); } public Task> SearchEpisodes(string searchQuery) { - return _episodes.Search(searchQuery); + return EpisodeRepository.Search(searchQuery); } public Task> SearchGenres(string searchQuery) { - return _genres.Search(searchQuery); + return GenreRepository.Search(searchQuery); } public Task> SearchStudios(string searchQuery) { - return _studios.Search(searchQuery); + return StudioRepository.Search(searchQuery); } public Task> SearchPeople(string searchQuery) { - return _people.Search(searchQuery); + return PeopleRepository.Search(searchQuery); } public Task RegisterLibrary(Library library) { - return _libraries.Create(library); + return LibraryRepository.Create(library); } public Task RegisterCollection(Collection collection) { - return _collections.Create(collection); + return CollectionRepository.Create(collection); } public Task RegisterShow(Show show) { - return _shows.Create(show); + return ShowRepository.Create(show); } public Task RegisterSeason(Season season) { - return _seasons.Create(season); + return SeasonRepository.Create(season); } public Task RegisterEpisode(Episode episode) { - return _episodes.Create(episode); + return EpisodeRepository.Create(episode); } public Task RegisterTrack(Track track) { - return _tracks.Create(track); + return TrackRepository.Create(track); } public Task RegisterGenre(Genre genre) { - return _genres.Create(genre); + return GenreRepository.Create(genre); } public Task RegisterStudio(Studio studio) { - return _studios.Create(studio); + return StudioRepository.Create(studio); } public Task RegisterPeople(People people) { - return _people.Create(people); + return PeopleRepository.Create(people); } public Task EditLibrary(Library library, bool resetOld) { - return _libraries.Edit(library, resetOld); + return LibraryRepository.Edit(library, resetOld); } public Task EditCollection(Collection collection, bool resetOld) { - return _collections.Edit(collection, resetOld); + return CollectionRepository.Edit(collection, resetOld); } public Task EditShow(Show show, bool resetOld) { - return _shows.Edit(show, resetOld); + return ShowRepository.Edit(show, resetOld); } public Task EditSeason(Season season, bool resetOld) { - return _seasons.Edit(season, resetOld); + return SeasonRepository.Edit(season, resetOld); } public Task EditEpisode(Episode episode, bool resetOld) { - return _episodes.Edit(episode, resetOld); + return EpisodeRepository.Edit(episode, resetOld); } public Task EditTrack(Track track, bool resetOld) { - return _tracks.Edit(track, resetOld); + return TrackRepository.Edit(track, resetOld); } public Task EditGenre(Genre genre, bool resetOld) { - return _genres.Edit(genre, resetOld); + return GenreRepository.Edit(genre, resetOld); } public Task EditStudio(Studio studio, bool resetOld) { - return _studios.Edit(studio, resetOld); + return StudioRepository.Edit(studio, resetOld); } public Task EditPeople(People people, bool resetOld) { - return _people.Edit(people, resetOld); + return PeopleRepository.Edit(people, resetOld); } public Task DelteLibrary(Library library) { - return _libraries.Delete(library); + return LibraryRepository.Delete(library); } public Task DeleteCollection(Collection collection) { - return _collections.Delete(collection); + return CollectionRepository.Delete(collection); } public Task DeleteShow(Show show) { - return _shows.Delete(show); + return ShowRepository.Delete(show); } public Task DeleteSeason(Season season) { - return _seasons.Delete(season); + return SeasonRepository.Delete(season); } public Task DeleteEpisode(Episode episode) { - return _episodes.Delete(episode); + return EpisodeRepository.Delete(episode); } public Task DeleteTrack(Track track) { - return _tracks.Delete(track); + return TrackRepository.Delete(track); } public Task DeleteGenre(Genre genre) { - return _genres.Delete(genre); + return GenreRepository.Delete(genre); } public Task DeleteStudio(Studio studio) { - return _studios.Delete(studio); + return StudioRepository.Delete(studio); } public Task DeletePeople(People people) { - return _people.Delete(people); + return PeopleRepository.Delete(people); } public Task DelteLibrary(string library) { - return _libraries.Delete(library); + return LibraryRepository.Delete(library); } public Task DeleteCollection(string collection) { - return _collections.Delete(collection); + return CollectionRepository.Delete(collection); } public Task DeleteShow(string show) { - return _shows.Delete(show); + return ShowRepository.Delete(show); } public Task DeleteSeason(string season) { - return _seasons.Delete(season); + return SeasonRepository.Delete(season); } public Task DeleteEpisode(string episode) { - return _episodes.Delete(episode); + return EpisodeRepository.Delete(episode); } public Task DeleteTrack(string track) { - return _tracks.Delete(track); + return TrackRepository.Delete(track); } public Task DeleteGenre(string genre) { - return _genres.Delete(genre); + return GenreRepository.Delete(genre); } public Task DeleteStudio(string studio) { - return _studios.Delete(studio); + return StudioRepository.Delete(studio); } public Task DeletePeople(string people) { - return _people.Delete(people); + return PeopleRepository.Delete(people); } public Task DelteLibrary(int library) { - return _libraries.Delete(library); + return LibraryRepository.Delete(library); } public Task DeleteCollection(int collection) { - return _collections.Delete(collection); + return CollectionRepository.Delete(collection); } public Task DeleteShow(int show) { - return _shows.Delete(show); + return ShowRepository.Delete(show); } public Task DeleteSeason(int season) { - return _seasons.Delete(season); + return SeasonRepository.Delete(season); } public Task DeleteEpisode(int episode) { - return _episodes.Delete(episode); + return EpisodeRepository.Delete(episode); } public Task DeleteTrack(int track) { - return _tracks.Delete(track); + return TrackRepository.Delete(track); } public Task DeleteGenre(int genre) { - return _genres.Delete(genre); + return GenreRepository.Delete(genre); } public Task DeleteStudio(int studio) { - return _studios.Delete(studio); + return StudioRepository.Delete(studio); } public Task DeletePeople(int people) { - return _people.Delete(people); + return PeopleRepository.Delete(people); } } } diff --git a/Kyoo.Common/Models/Collection.cs b/Kyoo.Common/Models/Ressources/Collection.cs similarity index 100% rename from Kyoo.Common/Models/Collection.cs rename to Kyoo.Common/Models/Ressources/Collection.cs diff --git a/Kyoo.Common/Models/Episode.cs b/Kyoo.Common/Models/Ressources/Episode.cs similarity index 100% rename from Kyoo.Common/Models/Episode.cs rename to Kyoo.Common/Models/Ressources/Episode.cs diff --git a/Kyoo.Common/Models/Genre.cs b/Kyoo.Common/Models/Ressources/Genre.cs similarity index 100% rename from Kyoo.Common/Models/Genre.cs rename to Kyoo.Common/Models/Ressources/Genre.cs diff --git a/Kyoo.Common/Models/IRessource.cs b/Kyoo.Common/Models/Ressources/IRessource.cs similarity index 100% rename from Kyoo.Common/Models/IRessource.cs rename to Kyoo.Common/Models/Ressources/IRessource.cs diff --git a/Kyoo.Common/Models/Library.cs b/Kyoo.Common/Models/Ressources/Library.cs similarity index 100% rename from Kyoo.Common/Models/Library.cs rename to Kyoo.Common/Models/Ressources/Library.cs diff --git a/Kyoo.Common/Models/People.cs b/Kyoo.Common/Models/Ressources/People.cs similarity index 100% rename from Kyoo.Common/Models/People.cs rename to Kyoo.Common/Models/Ressources/People.cs diff --git a/Kyoo.Common/Models/ProviderID.cs b/Kyoo.Common/Models/Ressources/ProviderID.cs similarity index 100% rename from Kyoo.Common/Models/ProviderID.cs rename to Kyoo.Common/Models/Ressources/ProviderID.cs diff --git a/Kyoo.Common/Models/Season.cs b/Kyoo.Common/Models/Ressources/Season.cs similarity index 100% rename from Kyoo.Common/Models/Season.cs rename to Kyoo.Common/Models/Ressources/Season.cs diff --git a/Kyoo.Common/Models/Show.cs b/Kyoo.Common/Models/Ressources/Show.cs similarity index 100% rename from Kyoo.Common/Models/Show.cs rename to Kyoo.Common/Models/Ressources/Show.cs diff --git a/Kyoo.Common/Models/Studio.cs b/Kyoo.Common/Models/Ressources/Studio.cs similarity index 100% rename from Kyoo.Common/Models/Studio.cs rename to Kyoo.Common/Models/Ressources/Studio.cs diff --git a/Kyoo.Common/Models/Track.cs b/Kyoo.Common/Models/Ressources/Track.cs similarity index 100% rename from Kyoo.Common/Models/Track.cs rename to Kyoo.Common/Models/Ressources/Track.cs diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 981b5d39..9ab38dce 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Configuration; namespace Kyoo.CommonApi @@ -50,15 +53,13 @@ namespace Kyoo.CommonApi [HttpGet] [Authorize(Policy = "Read")] public async Task>> GetAll([FromQuery] string sortBy, - [FromQuery] int limit, [FromQuery] int afterID, - [FromQuery] Dictionary where) + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) { where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); - if (limit == 0) - limit = 20; try { @@ -66,10 +67,7 @@ namespace Kyoo.CommonApi 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); + return Page(ressources, limit); } catch (ArgumentException ex) { @@ -77,6 +75,15 @@ namespace Kyoo.CommonApi } } + protected Page Page(ICollection ressources, int limit) + where TResult : IRessource + { + return new Page(ressources, + _baseURL + Request.Path, + Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), + limit); + } + [HttpPost] [Authorize(Policy = "Write")] public async Task> Create([FromBody] T ressource) @@ -91,6 +98,20 @@ namespace Kyoo.CommonApi return Conflict(existing); } } + + [HttpPut] + [Authorize(Policy = "Write")] + public async Task> Edit([FromQuery] bool resetOld, [FromBody] T ressource) + { + if (ressource.ID <= 0) + { + T old = await _repository.Get(ressource.Slug); + if (old == null) + return NotFound(); + ressource.ID = old.ID; + } + return await _repository.Edit(ressource, resetOld); + } [HttpPut("{id}")] [Authorize(Policy = "Write")] diff --git a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj index 8e7ca25d..7bd6a5d4 100644 --- a/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj +++ b/Kyoo.CommonAPI/Kyoo.CommonAPI.csproj @@ -6,6 +6,7 @@ Kyoo.CommonApi Kyoo.CommonApi AnonymusRaccoon + Library diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 9356d6c3..555538ab 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -45,12 +45,18 @@ namespace Kyoo.Controllers public abstract Task> Search(string query); - public virtual async Task> GetAll(Expression> where = null, + public Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ApplyFilters(_database.Set(), where, sort, limit); + } + + protected async Task> ApplyFilters(IQueryable query, + Expression> where = null, Sort sort = default, Pagination limit = default) { - IQueryable query = _database.Set(); - if (where != null) query = query.Where(where); diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 8ecb92ad..2d9fc938 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -102,14 +102,26 @@ namespace Kyoo.Controllers } } - public async Task> GetSeasons(int showID) + public Task> GetSeasons(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return await _database.Seasons.Where(x => x.ShowID == showID).ToListAsync(); + return ApplyFilters(_database.Seasons.Where(x => x.ShowID == showID), + where, + sort, + limit); } - public async Task> GetSeasons(string showSlug) + public Task> GetSeasons(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return await _database.Seasons.Where(x => x.Show.Slug == showSlug).ToListAsync(); + return ApplyFilters(_database.Seasons.Where(x => x.Show.Slug == showSlug), + where, + sort, + limit); } public async Task Delete(string showSlug, int seasonNumber) diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 06a578dd..4c3984b9 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -1,7 +1,7 @@ using System; using System.Reflection; using IdentityServer4.Services; -using Kyoo.API; +using Kyoo.Api; using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/Kyoo/Views/API/AccountAPI.cs b/Kyoo/Views/API/AccountAPI.cs index aa30b5d6..80eea5f0 100644 --- a/Kyoo/Views/API/AccountAPI.cs +++ b/Kyoo/Views/API/AccountAPI.cs @@ -16,7 +16,7 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; -namespace Kyoo.API +namespace Kyoo.Api { public class RegisterRequest { diff --git a/Kyoo/Views/API/CollectionAPI.cs b/Kyoo/Views/API/CollectionAPI.cs index 535fcc84..c4223e20 100644 --- a/Kyoo/Views/API/CollectionAPI.cs +++ b/Kyoo/Views/API/CollectionAPI.cs @@ -1,33 +1,20 @@ using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; using System.Threading.Tasks; +using Kyoo.CommonApi; using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; -namespace Kyoo.API +namespace Kyoo.Api { - [Route("api/[controller]")] + [Route("api/collection")] + [Route("api/collections")] [ApiController] - public class CollectionController : ControllerBase + public class CollectionApi : CrudApi { - private readonly ILibraryManager _libraryManager; - - public CollectionController(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - [HttpGet("{collectionSlug}")] - [Authorize(Policy="Read")] - public async Task> GetShows(string collectionSlug) - { - Collection collection = await _libraryManager.GetCollection(collectionSlug); - - if (collection == null) - return NotFound(); - - return collection; - } + public CollectionApi(ICollectionRepository repository, IConfiguration configuration) + : base(repository, configuration) + { } } } \ No newline at end of file diff --git a/Kyoo/Views/API/EpisodesAPI.cs b/Kyoo/Views/API/EpisodesAPI.cs index fad605b1..abf0650d 100644 --- a/Kyoo/Views/API/EpisodesAPI.cs +++ b/Kyoo/Views/API/EpisodesAPI.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/GenresAPI.cs b/Kyoo/Views/API/GenresAPI.cs index 98d27e7f..118c4fcc 100644 --- a/Kyoo/Views/API/GenresAPI.cs +++ b/Kyoo/Views/API/GenresAPI.cs @@ -5,7 +5,7 @@ using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/genres")] [Route("api/genre")] diff --git a/Kyoo/Views/API/LibrariesAPI.cs b/Kyoo/Views/API/LibrariesAPI.cs index 4fe33cad..eeeee468 100644 --- a/Kyoo/Views/API/LibrariesAPI.cs +++ b/Kyoo/Views/API/LibrariesAPI.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/libraries")] [Route("api/library")] diff --git a/Kyoo/Views/API/PeopleAPI.cs b/Kyoo/Views/API/PeopleAPI.cs index 59918418..ad27d8b9 100644 --- a/Kyoo/Views/API/PeopleAPI.cs +++ b/Kyoo/Views/API/PeopleAPI.cs @@ -5,7 +5,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/ProviderAPI.cs b/Kyoo/Views/API/ProviderAPI.cs index 019e304c..d1390e2c 100644 --- a/Kyoo/Views/API/ProviderAPI.cs +++ b/Kyoo/Views/API/ProviderAPI.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/provider")] [Route("api/providers")] diff --git a/Kyoo/Views/API/SearchAPI.cs b/Kyoo/Views/API/SearchAPI.cs index 3f1489fe..4efcf93a 100644 --- a/Kyoo/Views/API/SearchAPI.cs +++ b/Kyoo/Views/API/SearchAPI.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/ShowsAPI.cs b/Kyoo/Views/API/ShowsAPI.cs deleted file mode 100644 index abea4b1e..00000000 --- a/Kyoo/Views/API/ShowsAPI.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models.Exceptions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Configuration; - -namespace Kyoo.API -{ - [Route("api/shows")] - [Route("api/show")] - [ApiController] - public class ShowsAPI : ControllerBase - { - - } -} diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs new file mode 100644 index 00000000..4e62fb3b --- /dev/null +++ b/Kyoo/Views/API/ShowsApi.cs @@ -0,0 +1,85 @@ +using System; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/show")] + [Route("api/shows")] + [ApiController] + public class ShowsApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public ShowsApi(ILibraryManager libraryManager, + IConfiguration configuration) + : base(libraryManager.ShowRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{showID}/season")] + [HttpGet("{showID}/seasons")] + [Authorize(Policy = "Read")] + public async Task>> GetSeasons(int showID, + [FromQuery] string sortBy, + [FromQuery] int limit, + [FromQuery] int afterID, + [FromQuery] Dictionary where) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetSeasons(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/season")] + [HttpGet("{slug}/seasons")] + [Authorize(Policy = "Read")] + public async Task>> GetSeasons(string slug, + [FromQuery] string sortBy, + [FromQuery] int limit, + [FromQuery] int afterID, + [FromQuery] Dictionary where) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetSeasons(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} diff --git a/Kyoo/Views/API/StudioAPI.cs b/Kyoo/Views/API/StudioAPI.cs index 59228155..88efca86 100644 --- a/Kyoo/Views/API/StudioAPI.cs +++ b/Kyoo/Views/API/StudioAPI.cs @@ -5,7 +5,7 @@ using Kyoo.Controllers; using Kyoo.Models; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/studios")] [Route("api/studio")] diff --git a/Kyoo/Views/API/SubtitleAPI.cs b/Kyoo/Views/API/SubtitleAPI.cs index 2861b40f..3ffd72c3 100644 --- a/Kyoo/Views/API/SubtitleAPI.cs +++ b/Kyoo/Views/API/SubtitleAPI.cs @@ -8,7 +8,7 @@ using Kyoo.Controllers; using Kyoo.Models.Watch; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { [Route("[controller]")] [ApiController] diff --git a/Kyoo/Views/API/TaskAPI.cs b/Kyoo/Views/API/TaskAPI.cs index 4becbcd7..24ab8845 100644 --- a/Kyoo/Views/API/TaskAPI.cs +++ b/Kyoo/Views/API/TaskAPI.cs @@ -2,7 +2,7 @@ using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/[controller]")] [ApiController] diff --git a/Kyoo/Views/API/ThumbnailAPI.cs b/Kyoo/Views/API/ThumbnailAPI.cs index 3955cc6b..455c680d 100644 --- a/Kyoo/Views/API/ThumbnailAPI.cs +++ b/Kyoo/Views/API/ThumbnailAPI.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Kyoo.Controllers; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { public class ThumbnailController : ControllerBase { diff --git a/Kyoo/Views/API/VideoAPI.cs b/Kyoo/Views/API/VideoAPI.cs index 0570a03c..2c12cea7 100644 --- a/Kyoo/Views/API/VideoAPI.cs +++ b/Kyoo/Views/API/VideoAPI.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; -namespace Kyoo.API +namespace Kyoo.Api { [Route("[controller]")] [ApiController] diff --git a/Kyoo/Views/API/WatchAPI.cs b/Kyoo/Views/API/WatchAPI.cs index 7e44997d..07039073 100644 --- a/Kyoo/Views/API/WatchAPI.cs +++ b/Kyoo/Views/API/WatchAPI.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Kyoo.API +namespace Kyoo.Api { [Route("api/[controller]")] [ApiController] From 00ed7a48630c2be304b49a61ae00240edcc4ef18 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 20 Jul 2020 04:35:42 +0200 Subject: [PATCH 16/36] Fixing int requirement --- Kyoo.CommonAPI/CrudApi.cs | 6 +++--- Kyoo/Views/API/ShowsApi.cs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 9ab38dce..55d1ae33 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -26,7 +26,7 @@ namespace Kyoo.CommonApi _baseURL = configuration.GetValue("public_url").TrimEnd('/'); } - [HttpGet("{id}")] + [HttpGet("{id:int}")] [Authorize(Policy = "Read")] [JsonDetailed] public async Task> Get(int id) @@ -113,7 +113,7 @@ namespace Kyoo.CommonApi return await _repository.Edit(ressource, resetOld); } - [HttpPut("{id}")] + [HttpPut("{id:int}")] [Authorize(Policy = "Write")] public async Task> Edit(int id, [FromQuery] bool resetOld, [FromBody] T ressource) { @@ -139,7 +139,7 @@ namespace Kyoo.CommonApi return await _repository.Edit(ressource, resetOld); } - [HttpDelete("{id}")] + [HttpDelete("{id:int}")] [Authorize(Policy = "Write")] public async Task Delete(int id) { diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index 4e62fb3b..b74a398c 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -24,14 +24,14 @@ namespace Kyoo.Api _libraryManager = libraryManager; } - [HttpGet("{showID}/season")] - [HttpGet("{showID}/seasons")] + [HttpGet("{showID:int}/season")] + [HttpGet("{showID:int}/seasons")] [Authorize(Policy = "Read")] public async Task>> GetSeasons(int showID, [FromQuery] string sortBy, - [FromQuery] int limit, [FromQuery] int afterID, - [FromQuery] Dictionary where) + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) { where.Remove("showID"); where.Remove("sortBy"); @@ -58,9 +58,9 @@ namespace Kyoo.Api [Authorize(Policy = "Read")] public async Task>> GetSeasons(string slug, [FromQuery] string sortBy, - [FromQuery] int limit, [FromQuery] int afterID, - [FromQuery] Dictionary where) + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) { where.Remove("slug"); where.Remove("sortBy"); From 59e72b6dcae88bc3a300d30d8cb1d11fd9d2ccc1 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 24 Jul 2020 02:47:31 +0200 Subject: [PATCH 17/36] Adding episodes related info & NotFound exceptions --- Kyoo.Common/Controllers/ILibraryManager.cs | 56 ++++++++++- Kyoo.Common/Controllers/IRepository.cs | 56 ++++++++++- .../Implementations/LibraryManager.cs | 51 +++++++--- Kyoo.Common/Models/Exceptions/ItemNotFound.cs | 2 + .../Repositories/EpisodeRepository.cs | 95 ++++++++++++++++--- .../Repositories/SeasonRepository.cs | 27 +++++- .../Repositories/ShowRepository.cs | 1 + Kyoo/Views/API/EpisodesAPI.cs | 12 +-- Kyoo/Views/API/ShowsApi.cs | 75 +++++++++++++++ 9 files changed, 328 insertions(+), 47 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 33d65c42..a83721c0 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -55,11 +55,61 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetSeasons(showSlug, where, new Sort(sort), limit); - Task> GetEpisodes(int showID, int seasonNumber); - Task> GetEpisodes(string showSlug, int seasonNumber); - Task> GetEpisodes(int seasonID); + Task> GetEpisodes(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodes(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodes(showID, where, new Sort(sort), limit); + Task> GetEpisodes(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodes(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodes(showSlug, where, new Sort(sort), limit); + Task> GetEpisodesFromSeason(int seasonID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(int seasonID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(seasonID, where, new Sort(sort), limit); + + Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(showID, seasonNumber, where, new Sort(sort), limit); + + Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(showSlug, seasonNumber, where, new Sort(sort), limit); + + // Helpers Task AddShowLink(int showID, int? libraryID, int? collectionID); Task AddShowLink([NotNull] Show show, Library library, Collection collection); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 2c971a13..7588d652 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -106,6 +106,7 @@ namespace Kyoo.Controllers public interface ISeasonRepository : IRepository { + Task Get(int showID, int seasonNumber); Task Get(string showSlug, int seasonNumber); Task Delete(string showSlug, int seasonNumber); @@ -128,7 +129,6 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetSeasons(showSlug, where, new Sort(sort), limit); - } public interface IEpisodeRepository : IRepository @@ -136,9 +136,57 @@ namespace Kyoo.Controllers Task Get(string showSlug, int seasonNumber, int episodeNumber); Task Delete(string showSlug, int seasonNumber, int episodeNumber); - Task> GetEpisodes(int showID, int seasonNumber); - Task> GetEpisodes(string showSlug, int seasonNumber); - Task> GetEpisodes(int seasonID); + Task> GetEpisodes(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodes(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodes(showID, where, new Sort(sort), limit); + + Task> GetEpisodes(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodes(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodes(showSlug, where, new Sort(sort), limit); + + Task> GetEpisodesFromSeason(int seasonID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(int seasonID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(seasonID, where, new Sort(sort), limit); + Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(showID, seasonNumber, where, new Sort(sort), limit); + Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetEpisodesFromSeason(showSlug, seasonNumber, where, new Sort(sort), limit); } public interface ITrackRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 6956100c..52f3a600 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -213,21 +213,48 @@ namespace Kyoo.Controllers return SeasonRepository.GetSeasons(showSlug, where, sort, limit); } - public Task> GetEpisodes(int showID, int seasonNumber) + public Task> GetEpisodes(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return EpisodeRepository.GetEpisodes(showID, seasonNumber); - } - - public Task> GetEpisodes(string showSlug, int seasonNumber) - { - return EpisodeRepository.GetEpisodes(showSlug, seasonNumber); - } - - public Task> GetEpisodes(int seasonID) - { - return EpisodeRepository.GetEpisodes(seasonID); + return EpisodeRepository.GetEpisodes(showID, where, sort, limit); } + public Task> GetEpisodes(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return EpisodeRepository.GetEpisodes(showSlug, where, sort, limit); + } + + public Task> GetEpisodesFromSeason(int seasonID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return EpisodeRepository.GetEpisodesFromSeason(seasonID, where, sort, limit); + } + + public Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return EpisodeRepository.GetEpisodesFromSeason(showID, seasonNumber, where, sort, limit); + } + + public Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return EpisodeRepository.GetEpisodesFromSeason(showSlug, seasonNumber, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo.Common/Models/Exceptions/ItemNotFound.cs b/Kyoo.Common/Models/Exceptions/ItemNotFound.cs index c72427c6..f23cf363 100644 --- a/Kyoo.Common/Models/Exceptions/ItemNotFound.cs +++ b/Kyoo.Common/Models/Exceptions/ItemNotFound.cs @@ -6,6 +6,8 @@ namespace Kyoo.Models.Exceptions { public override string Message { get; } + public ItemNotFound() {} + public ItemNotFound(string message) { Message = message; diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 8c83be3a..cbb8cd43 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -14,13 +14,21 @@ namespace Kyoo.Controllers { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + private readonly IShowRepository _shows; + private readonly ISeasonRepository _seasons; protected override Expression> DefaultSort => x => x.EpisodeNumber; - public EpisodeRepository(DatabaseContext database, IProviderRepository providers) : base(database) + public EpisodeRepository(DatabaseContext database, + IProviderRepository providers, + IShowRepository shows, + ISeasonRepository seasons) + : base(database) { _database = database; _providers = providers; + _shows = shows; + _seasons = seasons; } @@ -105,23 +113,80 @@ namespace Kyoo.Controllers } } - public async Task> GetEpisodes(int showID, int seasonNumber) + public async Task> GetEpisodes(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { - return await _database.Episodes.Where(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber).ToListAsync(); - } - - public async Task> GetEpisodes(string showSlug, int seasonNumber) - { - return await _database.Episodes.Where(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber).ToListAsync(); - } - - public async Task> GetEpisodes(int seasonID) - { - return await _database.Episodes.Where(x => x.SeasonID == seasonID).ToListAsync(); + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.ShowID == showID), + where, + sort, + limit); + if (!episodes.Any() && await _shows.Get(showID) == null) + throw new ItemNotFound(); + return episodes; } + public async Task> GetEpisodes(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.Show.Slug == showSlug), + where, + sort, + limit); + if (!episodes.Any() && await _shows.Get(showSlug) == null) + throw new ItemNotFound(); + return episodes; + } + + public async Task> GetEpisodesFromSeason(int seasonID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.SeasonID == seasonID), + where, + sort, + limit); + if (!episodes.Any() && await _seasons.Get(seasonID) == null) + throw new ItemNotFound(); + return episodes; + } + + public async Task> GetEpisodesFromSeason(int showID, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber), + where, + sort, + limit); + if (!episodes.Any() && await _seasons.Get(showID, seasonNumber) == null) + throw new ItemNotFound(); + return episodes; + } + + public async Task> GetEpisodesFromSeason(string showSlug, + int seasonNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection episodes = await ApplyFilters(_database.Episodes.Where(x => x.Show.Slug == showSlug + && x.SeasonNumber == seasonNumber), + where, + sort, + limit); + if (!episodes.Any() && await _seasons.Get(showSlug, seasonNumber) == null) + throw new ItemNotFound(); + return episodes; + } + public async Task Delete(string showSlug, int seasonNumber, int episodeNumber) { Episode obj = await Get(showSlug, seasonNumber, episodeNumber); diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 2d9fc938..cec35eeb 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -15,15 +15,20 @@ namespace Kyoo.Controllers private readonly DatabaseContext _database; private readonly IProviderRepository _providers; private readonly IEpisodeRepository _episodes; + private readonly IShowRepository _shows; protected override Expression> DefaultSort => x => x.SeasonNumber; - public SeasonRepository(DatabaseContext database, IProviderRepository providers, IEpisodeRepository episodes) + public SeasonRepository(DatabaseContext database, + IProviderRepository providers, + IEpisodeRepository episodes, + IShowRepository shows) : base(database) { _database = database; _providers = providers; _episodes = episodes; + _shows = shows; } @@ -50,6 +55,12 @@ namespace Kyoo.Controllers return Get(match.Groups["show"].Value, int.Parse(match.Groups["season"].Value)); } + public Task Get(int showID, int seasonNumber) + { + return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber); + } + public Task Get(string showSlug, int seasonNumber) { return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug @@ -102,26 +113,32 @@ namespace Kyoo.Controllers } } - public Task> GetSeasons(int showID, + public async Task> GetSeasons(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(_database.Seasons.Where(x => x.ShowID == showID), + ICollection seasons = await ApplyFilters(_database.Seasons.Where(x => x.ShowID == showID), where, sort, limit); + if (!seasons.Any() && await _shows.Get(showID) == null) + throw new ItemNotFound(); + return seasons; } - public Task> GetSeasons(string showSlug, + public async Task> GetSeasons(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) { - return ApplyFilters(_database.Seasons.Where(x => x.Show.Slug == showSlug), + ICollection seasons = await ApplyFilters(_database.Seasons.Where(x => x.Show.Slug == showSlug), where, sort, limit); + if (!seasons.Any() && await _shows.Get(showSlug) == null) + throw new ItemNotFound(); + return seasons; } public async Task Delete(string showSlug, int seasonNumber) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index b0335786..3e9d9710 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -171,6 +171,7 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync(); + // TODO fix circular references of Show/Season/Episode Repository. if (obj.Seasons != null) await _seasons.DeleteRange(obj.Seasons); diff --git a/Kyoo/Views/API/EpisodesAPI.cs b/Kyoo/Views/API/EpisodesAPI.cs index abf0650d..736ffb02 100644 --- a/Kyoo/Views/API/EpisodesAPI.cs +++ b/Kyoo/Views/API/EpisodesAPI.cs @@ -1,4 +1,5 @@ -using Kyoo.Models; +using System; +using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; @@ -21,14 +22,9 @@ namespace Kyoo.Api [HttpGet("{showSlug}/season/{seasonNumber}")] [Authorize(Policy="Read")] - public async Task>> GetEpisodesForSeason(string showSlug, int seasonNumber) + public Task>> GetEpisodesForSeason(string showSlug, int seasonNumber) { - IEnumerable episodes = await _libraryManager.GetEpisodes(showSlug, seasonNumber); - - if(episodes == null) - return NotFound(); - - return episodes.ToList(); + throw new NotImplementedException(); } [HttpGet("{showSlug}/season/{seasonNumber}/episode/{episodeNumber}")] diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index b74a398c..cf7d300b 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; @@ -47,6 +48,10 @@ namespace Kyoo.Api return Page(ressources, limit); } + catch (ItemNotFound) + { + return NotFound(); + } catch (ArgumentException ex) { return BadRequest(new {Error = ex.Message}); @@ -76,6 +81,76 @@ namespace Kyoo.Api return Page(ressources, limit); } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showID:int}/episode")] + [HttpGet("{showID:int}/episodes")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisodes(int showID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetEpisodes(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/episode")] + [HttpGet("{slug}/episodes")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisodes(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetEpisodes(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } catch (ArgumentException ex) { return BadRequest(new {Error = ex.Message}); From c47a4b2e15cd1e46f95df947d8857d00ab9041a7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 24 Jul 2020 20:34:56 +0200 Subject: [PATCH 18/36] Fixing circular dependencies --- .../Repositories/SeasonRepository.cs | 17 +++++---- .../Repositories/ShowRepository.cs | 35 ++++++++++--------- Kyoo/Startup.cs | 20 +++++------ 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index cec35eeb..f197d7c8 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -14,20 +15,20 @@ namespace Kyoo.Controllers { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; - private readonly IEpisodeRepository _episodes; + private readonly Lazy _episodes; private readonly IShowRepository _shows; protected override Expression> DefaultSort => x => x.SeasonNumber; public SeasonRepository(DatabaseContext database, IProviderRepository providers, - IEpisodeRepository episodes, - IShowRepository shows) + IShowRepository shows, + IServiceProvider services) : base(database) { _database = database; _providers = providers; - _episodes = episodes; + _episodes = new Lazy(services.GetRequiredService); _shows = shows; } @@ -36,14 +37,16 @@ namespace Kyoo.Controllers { _database.Dispose(); _providers.Dispose(); - _episodes.Dispose(); + if (_episodes.IsValueCreated) + _episodes.Value.Dispose(); } public override async ValueTask DisposeAsync() { await _database.DisposeAsync(); await _providers.DisposeAsync(); - await _episodes.DisposeAsync(); + if (_episodes.IsValueCreated) + await _episodes.Value.DisposeAsync(); } public override Task Get(string slug) @@ -161,7 +164,7 @@ namespace Kyoo.Controllers await _database.SaveChangesAsync(); if (obj.Episodes != null) - await _episodes.DeleteRange(obj.Episodes); + await _episodes.Value.DeleteRange(obj.Episodes); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 3e9d9710..5d9f3af8 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -16,17 +17,16 @@ namespace Kyoo.Controllers private readonly IPeopleRepository _people; private readonly IGenreRepository _genres; private readonly IProviderRepository _providers; - private readonly ISeasonRepository _seasons; - private readonly IEpisodeRepository _episodes; + private readonly Lazy _seasons; + private readonly Lazy _episodes; protected override Expression> DefaultSort => x => x.Title; public ShowRepository(DatabaseContext database, IStudioRepository studios, IPeopleRepository people, IGenreRepository genres, - IProviderRepository providers, - ISeasonRepository seasons, - IEpisodeRepository episodes) + IProviderRepository providers, + IServiceProvider services) : base(database) { _database = database; @@ -34,8 +34,8 @@ namespace Kyoo.Controllers _people = people; _genres = genres; _providers = providers; - _seasons = seasons; - _episodes = episodes; + _seasons = new Lazy(services.GetRequiredService); + _episodes = new Lazy(services.GetRequiredService); } public override void Dispose() @@ -45,8 +45,10 @@ namespace Kyoo.Controllers _people.Dispose(); _genres.Dispose(); _providers.Dispose(); - _seasons.Dispose(); - _episodes.Dispose(); + if (_seasons.IsValueCreated) + _seasons.Value.Dispose(); + if (_episodes.IsValueCreated) + _episodes.Value.Dispose(); } public override async ValueTask DisposeAsync() @@ -56,8 +58,10 @@ namespace Kyoo.Controllers await _people.DisposeAsync(); await _genres.DisposeAsync(); await _providers.DisposeAsync(); - await _seasons.DisposeAsync(); - await _episodes.DisposeAsync(); + if (_seasons.IsValueCreated) + await _seasons.Value.DisposeAsync(); + if (_episodes.IsValueCreated) + await _episodes.Value.DisposeAsync(); } public override async Task> Search(string query) @@ -168,15 +172,14 @@ namespace Kyoo.Controllers if (obj.LibraryLinks != null) foreach (LibraryLink entry in obj.LibraryLinks) _database.Entry(entry).State = EntityState.Deleted; - - await _database.SaveChangesAsync(); - // TODO fix circular references of Show/Season/Episode Repository. + await _database.SaveChangesAsync(); + if (obj.Seasons != null) - await _seasons.DeleteRange(obj.Seasons); + await _seasons.Value.DeleteRange(obj.Seasons); if (obj.Episodes != null) - await _episodes.DeleteRange(obj.Episodes); + await _episodes.Value.DeleteRange(obj.Episodes); } } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 4c3984b9..b0c08eb5 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -133,16 +133,16 @@ namespace Kyoo }); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); From b73be95f11cc3692b37b302aeeecf62df9fffa1c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 25 Jul 2020 03:47:30 +0200 Subject: [PATCH 19/36] Adding shows/people route & GetPeopleByShow --- Kyoo.Common/Controllers/ILibraryManager.cs | 56 +++++++++----- Kyoo.Common/Controllers/IRepository.cs | 72 +++++++++++------- .../Implementations/LibraryManager.cs | 42 +++++++---- Kyoo.Common/Models/PeopleLink.cs | 2 +- Kyoo.Common/Models/Ressources/ProviderID.cs | 10 ++- Kyoo.CommonAPI/LocalRepository.cs | 20 +++-- Kyoo/Controllers/ProviderManager.cs | 4 +- .../Repositories/EpisodeRepository.cs | 10 +-- .../Repositories/PeopleRepository.cs | 64 +++++++++++++++- .../Repositories/ProviderRepository.cs | 4 +- .../Repositories/SeasonRepository.cs | 4 +- Kyoo/Models/DatabaseContext.cs | 4 +- ....cs => 20200724211017_Initial.Designer.cs} | 9 ++- ...3_Initial.cs => 20200724211017_Initial.cs} | 31 ++++---- .../Internal/DatabaseContextModelSnapshot.cs | 7 +- Kyoo/Tasks/Crawler.cs | 2 +- Kyoo/Tasks/MetadataLoader.cs | 2 +- Kyoo/Views/API/ShowsApi.cs | 74 +++++++++++++++++-- 18 files changed, 312 insertions(+), 105 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20200623233713_Initial.Designer.cs => 20200724211017_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20200623233713_Initial.cs => 20200724211017_Initial.cs} (97%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index a83721c0..c28daa61 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -36,44 +36,44 @@ namespace Kyoo.Controllers Task GetPeople(string slug); // Get by relations - Task> GetSeasons(int showID, + Task> GetSeasonsFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetSeasons(int showID, + Task> GetSeasonsFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetSeasons(showID, where, new Sort(sort), limit); - Task> GetSeasons(string showSlug, + ) => GetSeasonsFromShow(showID, where, new Sort(sort), limit); + Task> GetSeasonsFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetSeasons(string showSlug, + Task> GetSeasonsFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetSeasons(showSlug, where, new Sort(sort), limit); + ) => GetSeasonsFromShow(showSlug, where, new Sort(sort), limit); - Task> GetEpisodes(int showID, + Task> GetEpisodesFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodes(int showID, + Task> GetEpisodesFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodes(showID, where, new Sort(sort), limit); + ) => GetEpisodesFromShow(showID, where, new Sort(sort), limit); - Task> GetEpisodes(string showSlug, + Task> GetEpisodesFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodes(string showSlug, + Task> GetEpisodesFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodes(showSlug, where, new Sort(sort), limit); + ) => GetEpisodesFromShow(showSlug, where, new Sort(sort), limit); Task> GetEpisodesFromSeason(int seasonID, Expression> where = null, @@ -108,6 +108,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetEpisodesFromSeason(showSlug, seasonNumber, where, new Sort(sort), limit); + + Task> GetPeopleFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetPeopleFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetPeopleFromShow(showID, where, new Sort(sort), limit); + + Task> GetPeopleFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetPeopleFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); // Helpers @@ -124,10 +144,10 @@ namespace Kyoo.Controllers Task> GetShows(Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetSeasons(Expression> where = null, + Task> GetSeasonsFromShow(Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodes(Expression> where = null, + Task> GetEpisodesFromShow(Expression> where = null, Sort sort = default, Pagination limit = default); Task> GetTracks(Expression> where = null, @@ -158,14 +178,14 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetShows(where, new Sort(sort), limit); - Task> GetSeasons([Optional] Expression> where, + Task> GetSeasonsFromShow([Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetSeasons(where, new Sort(sort), limit); - Task> GetEpisodes([Optional] Expression> where, + ) => GetSeasonsFromShow(where, new Sort(sort), limit); + Task> GetEpisodesFromShow([Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodes(where, new Sort(sort), limit); + ) => GetEpisodesFromShow(where, new Sort(sort), limit); Task> GetTracks([Optional] Expression> where, Expression> sort, Pagination limit = default diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 7588d652..d72908d2 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -23,10 +23,10 @@ namespace Kyoo.Controllers public static implicit operator Pagination(int limit) => new Pagination(limit); } - public readonly struct Sort + public struct Sort { - public Expression> Key { get; } - public bool Descendant { get; } + public Expression> Key; + public bool Descendant; public Sort(Expression> key, bool descendant = false) { @@ -110,25 +110,25 @@ namespace Kyoo.Controllers Task Get(string showSlug, int seasonNumber); Task Delete(string showSlug, int seasonNumber); - Task> GetSeasons(int showID, + Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetSeasons(int showID, + Task> GetFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetSeasons(showID, where, new Sort(sort), limit); + ) => GetFromShow(showID, where, new Sort(sort), limit); - Task> GetSeasons(string showSlug, + Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetSeasons(string showSlug, + Task> GetFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetSeasons(showSlug, where, new Sort(sort), limit); + ) => GetFromShow(showSlug, where, new Sort(sort), limit); } public interface IEpisodeRepository : IRepository @@ -136,57 +136,57 @@ namespace Kyoo.Controllers Task Get(string showSlug, int seasonNumber, int episodeNumber); Task Delete(string showSlug, int seasonNumber, int episodeNumber); - Task> GetEpisodes(int showID, + Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodes(int showID, + Task> GetFromShow(int showID, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodes(showID, where, new Sort(sort), limit); + ) => GetFromShow(showID, where, new Sort(sort), limit); - Task> GetEpisodes(string showSlug, + Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodes(string showSlug, + Task> GetFromShow(string showSlug, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodes(showSlug, where, new Sort(sort), limit); + ) => GetFromShow(showSlug, where, new Sort(sort), limit); - Task> GetEpisodesFromSeason(int seasonID, + Task> GetFromSeason(int seasonID, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodesFromSeason(int seasonID, + Task> GetFromSeason(int seasonID, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodesFromSeason(seasonID, where, new Sort(sort), limit); - Task> GetEpisodesFromSeason(int showID, + ) => GetFromSeason(seasonID, where, new Sort(sort), limit); + Task> GetFromSeason(int showID, int seasonNumber, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodesFromSeason(int showID, + Task> GetFromSeason(int showID, int seasonNumber, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodesFromSeason(showID, seasonNumber, where, new Sort(sort), limit); - Task> GetEpisodesFromSeason(string showSlug, + ) => GetFromSeason(showID, seasonNumber, where, new Sort(sort), limit); + Task> GetFromSeason(string showSlug, int seasonNumber, Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetEpisodesFromSeason(string showSlug, + Task> GetFromSeason(string showSlug, int seasonNumber, [Optional] Expression> where, Expression> sort, Pagination limit = default - ) => GetEpisodesFromSeason(showSlug, seasonNumber, where, new Sort(sort), limit); + ) => GetFromSeason(showSlug, seasonNumber, where, new Sort(sort), limit); } public interface ITrackRepository : IRepository @@ -197,6 +197,28 @@ namespace Kyoo.Controllers public interface ICollectionRepository : IRepository {} public interface IGenreRepository : IRepository {} public interface IStudioRepository : IRepository {} - public interface IPeopleRepository : IRepository {} + + public interface IPeopleRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } public interface IProviderRepository : IRepository {} } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 52f3a600..b46028ee 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -148,14 +148,14 @@ namespace Kyoo.Controllers return ShowRepository.GetAll(where, sort, limit); } - public Task> GetSeasons(Expression> where = null, + public Task> GetSeasonsFromShow(Expression> where = null, Sort sort = default, Pagination page = default) { return SeasonRepository.GetAll(where, sort, page); } - public Task> GetEpisodes(Expression> where = null, + public Task> GetEpisodesFromShow(Expression> where = null, Sort sort = default, Pagination page = default) { @@ -197,36 +197,36 @@ namespace Kyoo.Controllers return ProviderRepository.GetAll(where, sort, page); } - public Task> GetSeasons(int showID, + public Task> GetSeasonsFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) { - return SeasonRepository.GetSeasons(showID, where, sort, limit); + return SeasonRepository.GetFromShow(showID, where, sort, limit); } - public Task> GetSeasons(string showSlug, + public Task> GetSeasonsFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) { - return SeasonRepository.GetSeasons(showSlug, where, sort, limit); + return SeasonRepository.GetFromShow(showSlug, where, sort, limit); } - public Task> GetEpisodes(int showID, + public Task> GetEpisodesFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) { - return EpisodeRepository.GetEpisodes(showID, where, sort, limit); + return EpisodeRepository.GetFromShow(showID, where, sort, limit); } - public Task> GetEpisodes(string showSlug, + public Task> GetEpisodesFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) { - return EpisodeRepository.GetEpisodes(showSlug, where, sort, limit); + return EpisodeRepository.GetFromShow(showSlug, where, sort, limit); } public Task> GetEpisodesFromSeason(int seasonID, @@ -234,7 +234,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - return EpisodeRepository.GetEpisodesFromSeason(seasonID, where, sort, limit); + return EpisodeRepository.GetFromSeason(seasonID, where, sort, limit); } public Task> GetEpisodesFromSeason(int showID, @@ -243,7 +243,7 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - return EpisodeRepository.GetEpisodesFromSeason(showID, seasonNumber, where, sort, limit); + return EpisodeRepository.GetFromSeason(showID, seasonNumber, where, sort, limit); } public Task> GetEpisodesFromSeason(string showSlug, @@ -252,7 +252,23 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - return EpisodeRepository.GetEpisodesFromSeason(showSlug, seasonNumber, where, sort, limit); + return EpisodeRepository.GetFromSeason(showSlug, seasonNumber, where, sort, limit); + } + + public Task> GetPeopleFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return PeopleRepository.GetFromShow(showID, where, sort, limit); + } + + public Task> GetPeopleFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } public Task AddShowLink(int showID, int? libraryID, int? collectionID) diff --git a/Kyoo.Common/Models/PeopleLink.cs b/Kyoo.Common/Models/PeopleLink.cs index 509f2b93..d36f90bd 100644 --- a/Kyoo.Common/Models/PeopleLink.cs +++ b/Kyoo.Common/Models/PeopleLink.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class PeopleLink + public class PeopleLink : IRessource { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int PeopleID { get; set; } diff --git a/Kyoo.Common/Models/Ressources/ProviderID.cs b/Kyoo.Common/Models/Ressources/ProviderID.cs index b7e6479a..12753fe2 100644 --- a/Kyoo.Common/Models/Ressources/ProviderID.cs +++ b/Kyoo.Common/Models/Ressources/ProviderID.cs @@ -5,15 +5,23 @@ namespace Kyoo.Models public class ProviderID : IRessource { [JsonIgnore] public int ID { get; set; } - public string Slug => Name; + public string Slug { get; set; } public string Name { get; set; } public string Logo { get; set; } public ProviderID() { } + public ProviderID(string name, string logo) + { + Slug = Utility.ToSlug(name); + Name = name; + Logo = logo; + } + public ProviderID(int id, string name, string logo) { ID = id; + Slug = Utility.ToSlug(name); Name = name; Logo = logo; } diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 555538ab..1842efe1 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -52,24 +52,34 @@ namespace Kyoo.Controllers return ApplyFilters(_database.Set(), where, sort, limit); } - protected async Task> ApplyFilters(IQueryable query, + protected Task> ApplyFilters(IQueryable query, Expression> where = null, Sort sort = default, Pagination limit = default) + { + return ApplyFilters(query, Get, DefaultSort, where, sort, limit); + } + + protected async Task> ApplyFilters(IQueryable query, + Func> get, + Expression> defaultSort, + Expression> where = null, + Sort sort = default, + Pagination limit = default) { if (where != null) query = query.Where(where); - Expression> sortKey = sort.Key ?? DefaultSort; + Expression> sortKey = sort.Key ?? defaultSort; query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); if (limit.AfterID != 0) { - T after = await Get(limit.AfterID); + TValue after = await get(limit.AfterID); object afterObj = sortKey.Compile()(after); - query = query.Where(Expression.Lambda>( + query = query.Where(Expression.Lambda>( ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)), - (ParameterExpression)((MemberExpression)sortKey.Body).Expression + sortKey.Parameters.First() )); } if (limit.Count > 0) diff --git a/Kyoo/Controllers/ProviderManager.cs b/Kyoo/Controllers/ProviderManager.cs index 663898d1..a614e39a 100644 --- a/Kyoo/Controllers/ProviderManager.cs +++ b/Kyoo/Controllers/ProviderManager.cs @@ -21,7 +21,7 @@ namespace Kyoo.Controllers T ret = new T(); IEnumerable providers = library?.Providers - .Select(x => _providers.FirstOrDefault(y => y.Provider.Name == x.Name)) + .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) .Where(x => x != null) ?? _providers; @@ -47,7 +47,7 @@ namespace Kyoo.Controllers List ret = new List(); IEnumerable providers = library?.Providers - .Select(x => _providers.FirstOrDefault(y => y.Provider.Name == x.Name)) + .Select(x => _providers.FirstOrDefault(y => y.Provider.Slug == x.Slug)) .Where(x => x != null) ?? _providers; diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index cbb8cd43..0392564c 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -113,7 +113,7 @@ namespace Kyoo.Controllers } } - public async Task> GetEpisodes(int showID, + public async Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -127,7 +127,7 @@ namespace Kyoo.Controllers return episodes; } - public async Task> GetEpisodes(string showSlug, + public async Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -141,7 +141,7 @@ namespace Kyoo.Controllers return episodes; } - public async Task> GetEpisodesFromSeason(int seasonID, + public async Task> GetFromSeason(int seasonID, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -155,7 +155,7 @@ namespace Kyoo.Controllers return episodes; } - public async Task> GetEpisodesFromSeason(int showID, + public async Task> GetFromSeason(int showID, int seasonNumber, Expression> where = null, Sort sort = default, @@ -171,7 +171,7 @@ namespace Kyoo.Controllers return episodes; } - public async Task> GetEpisodesFromSeason(string showSlug, + public async Task> GetFromSeason(string showSlug, int seasonNumber, Expression> where = null, Sort sort = default, diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 77b5f356..b7b97572 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -13,12 +14,15 @@ namespace Kyoo.Controllers { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + private readonly Lazy _shows; protected override Expression> DefaultSort => x => x.Name; - public PeopleRepository(DatabaseContext database, IProviderRepository providers) : base(database) + public PeopleRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) + : base(database) { _database = database; _providers = providers; + _shows = new Lazy(services.GetRequiredService); } @@ -26,17 +30,21 @@ namespace Kyoo.Controllers { _database.Dispose(); _providers.Dispose(); + if (_shows.IsValueCreated) + _shows.Value.Dispose(); } public override async ValueTask DisposeAsync() { await _database.DisposeAsync(); await _providers.DisposeAsync(); + if (_shows.IsValueCreated) + await _shows.Value.DisposeAsync(); } public override async Task> Search(string query) { - return await _database.Peoples + return await _database.People .Where(people => EF.Functions.ILike(people.Name, $"%{query}%")) .Take(20) .ToListAsync(); @@ -89,5 +97,57 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + if (sort.Key?.Body is MemberExpression member) + { + sort.Key = member.Member.Name switch + { + "Name" => x => x.People.Name, + "Slug" => x => x.People.Slug, + _ => sort.Key + }; + } + + ICollection people = await ApplyFilters(_database.PeopleLinks.Where(x => x.ShowID == showID), + id => _database.PeopleLinks.FirstOrDefaultAsync(x => x.ID == id), + x => x.People.Name, + where, + sort, + limit); + if (!people.Any() && await _shows.Value.Get(showID) == null) + throw new ItemNotFound(); + return people; + } + + public async Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + if (sort.Key?.Body is MemberExpression member) + { + sort.Key = member.Member.Name switch + { + "Name" => x => x.People.Name, + "Slug" => x => x.People.Slug, + _ => sort.Key + }; + } + + ICollection people = await ApplyFilters(_database.PeopleLinks.Where(x => x.Show.Slug == showSlug), + id => _database.PeopleLinks.FirstOrDefaultAsync(x => x.ID == id), + x => x.People.Name, + where, + sort, + limit); + if (!people.Any() && await _shows.Value.Get(showSlug) == null) + throw new ItemNotFound(); + return people; + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index f1477578..a74dc9b5 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -43,13 +43,13 @@ namespace Kyoo.Controllers { _database.DiscardChanges(); if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated provider (name {obj.Name} already exists)."); + throw new DuplicatedItemException($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); throw; } return obj; } - + protected override Task Validate(ProviderID ressource) { return Task.CompletedTask; diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index f197d7c8..5668a25d 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -116,7 +116,7 @@ namespace Kyoo.Controllers } } - public async Task> GetSeasons(int showID, + public async Task> GetFromShow(int showID, Expression> where = null, Sort sort = default, Pagination limit = default) @@ -130,7 +130,7 @@ namespace Kyoo.Controllers return seasons; } - public async Task> GetSeasons(string showSlug, + public async Task> GetFromShow(string showSlug, Expression> where = null, Sort sort = default, Pagination limit = default) diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index a6e657f7..ac54a80c 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -60,7 +60,7 @@ namespace Kyoo public DbSet Episodes { get; set; } public DbSet Tracks { get; set; } public DbSet Genres { get; set; } - public DbSet Peoples { get; set; } + public DbSet People { get; set; } public DbSet Studios { get; set; } public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } @@ -167,7 +167,7 @@ namespace Kyoo .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => x.Name) + .HasIndex(x => x.Slug) .IsUnique(); modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber}) diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs index a8f29bf5..36e43ea8 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20200623233713_Initial")] + [Migration("20200724211017_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -274,7 +274,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("Slug") .IsUnique(); - b.ToTable("Peoples"); + b.ToTable("People"); }); modelBuilder.Entity("Kyoo.Models.PeopleLink", b => @@ -318,9 +318,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); + b.Property("Slug") + .HasColumnType("text"); + b.HasKey("ID"); - b.HasIndex("Name") + b.HasIndex("Slug") .IsUnique(); b.ToTable("Providers"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs similarity index 97% rename from Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs index 4bdd56d9..93aadbae 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200623233713_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs @@ -55,7 +55,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateTable( - name: "Peoples", + name: "People", columns: table => new { ID = table.Column(nullable: false) @@ -66,7 +66,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }, constraints: table => { - table.PrimaryKey("PK_Peoples", x => x.ID); + table.PrimaryKey("PK_People", x => x.ID); }); migrationBuilder.CreateTable( @@ -75,6 +75,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal { ID = table.Column(nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Slug = table.Column(nullable: true), Name = table.Column(nullable: true), Logo = table.Column(nullable: true) }, @@ -253,9 +254,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal { table.PrimaryKey("PK_PeopleLinks", x => x.ID); table.ForeignKey( - name: "FK_PeopleLinks_Peoples_PeopleID", + name: "FK_PeopleLinks_People_PeopleID", column: x => x.PeopleID, - principalTable: "Peoples", + principalTable: "People", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( @@ -349,9 +350,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_MetadataIds_Peoples_PeopleID", + name: "FK_MetadataIds_People_PeopleID", column: x => x.PeopleID, - principalTable: "Peoples", + principalTable: "People", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( @@ -485,6 +486,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal table: "MetadataIds", column: "ShowID"); + migrationBuilder.CreateIndex( + name: "IX_People_Slug", + table: "People", + column: "Slug", + unique: true); + migrationBuilder.CreateIndex( name: "IX_PeopleLinks_PeopleID", table: "PeopleLinks", @@ -495,12 +502,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal table: "PeopleLinks", column: "ShowID"); - migrationBuilder.CreateIndex( - name: "IX_Peoples_Slug", - table: "Peoples", - column: "Slug", - unique: true); - migrationBuilder.CreateIndex( name: "IX_ProviderLinks_LibraryID", table: "ProviderLinks", @@ -512,9 +513,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal column: "ProviderID"); migrationBuilder.CreateIndex( - name: "IX_Providers_Name", + name: "IX_Providers_Slug", table: "Providers", - column: "Name", + column: "Slug", unique: true); migrationBuilder.CreateIndex( @@ -576,7 +577,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "Collections"); migrationBuilder.DropTable( - name: "Peoples"); + name: "People"); migrationBuilder.DropTable( name: "Libraries"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 2479152f..7e9761b4 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -272,7 +272,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("Slug") .IsUnique(); - b.ToTable("Peoples"); + b.ToTable("People"); }); modelBuilder.Entity("Kyoo.Models.PeopleLink", b => @@ -316,9 +316,12 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); + b.Property("Slug") + .HasColumnType("text"); + b.HasKey("ID"); - b.HasIndex("Name") + b.HasIndex("Slug") .IsUnique(); b.ToTable("Providers"); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index 34468068..bc3b2eeb 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -63,7 +63,7 @@ namespace Kyoo.Controllers if (!Directory.Exists(show.Path)) await libraryManager.DeleteShow(show); - ICollection episodes = await libraryManager.GetEpisodes(); + ICollection episodes = await libraryManager.GetEpisodesFromShow(); ICollection libraries = argument == null ? await libraryManager.GetLibraries() : new [] { await libraryManager.GetLibrary(argument)}; diff --git a/Kyoo/Tasks/MetadataLoader.cs b/Kyoo/Tasks/MetadataLoader.cs index c66702c5..3d272217 100644 --- a/Kyoo/Tasks/MetadataLoader.cs +++ b/Kyoo/Tasks/MetadataLoader.cs @@ -23,7 +23,7 @@ namespace Kyoo.Tasks DatabaseContext database = serviceScope.ServiceProvider.GetService(); IPluginManager pluginManager = serviceScope.ServiceProvider.GetService(); foreach (IMetadataProvider provider in pluginManager.GetPlugins()) - database.Providers.AddIfNotExist(provider.Provider, x => x.Name == provider.Provider.Name); + database.Providers.AddIfNotExist(provider.Provider, x => x.Slug == provider.Provider.Slug); database.SaveChanges(); return Task.CompletedTask; } diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index cf7d300b..f04973df 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -41,7 +41,7 @@ namespace Kyoo.Api try { - ICollection ressources = await _libraryManager.GetSeasons(showID, + ICollection ressources = await _libraryManager.GetSeasonsFromShow(showID, ApiHelper.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); @@ -74,7 +74,7 @@ namespace Kyoo.Api try { - ICollection ressources = await _libraryManager.GetSeasons(slug, + ICollection ressources = await _libraryManager.GetSeasonsFromShow(slug, ApiHelper.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); @@ -107,7 +107,7 @@ namespace Kyoo.Api try { - ICollection ressources = await _libraryManager.GetEpisodes(showID, + ICollection ressources = await _libraryManager.GetEpisodesFromShow(showID, ApiHelper.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); @@ -131,7 +131,7 @@ namespace Kyoo.Api [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) + [FromQuery] int limit = 50) { where.Remove("slug"); where.Remove("sortBy"); @@ -140,7 +140,7 @@ namespace Kyoo.Api try { - ICollection ressources = await _libraryManager.GetEpisodes(slug, + ICollection ressources = await _libraryManager.GetEpisodesFromShow(slug, ApiHelper.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); @@ -156,5 +156,69 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{showID:int}/people")] + [Authorize(Policy = "Read")] + public async Task>> GetPeople(int showID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetPeopleFromShow(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/people")] + [Authorize(Policy = "Read")] + public async Task>> GetPeople(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetPeopleFromShow(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } } } From fb2d5b24bd50c2d4017ac09004a8c9ecd85baefe Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 25 Jul 2020 04:22:27 +0200 Subject: [PATCH 20/36] Implementing shows/genres & GetGenresByShow --- Kyoo.Common/Controllers/ILibraryManager.cs | 20 ++++++ Kyoo.Common/Controllers/IRepository.cs | 24 ++++++- .../Implementations/LibraryManager.cs | 16 +++++ .../Repositories/GenreRepository.cs | 35 +++++++++- Kyoo/Views/API/ShowsApi.cs | 66 +++++++++++++++++++ 5 files changed, 159 insertions(+), 2 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index c28daa61..aac86eff 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -128,6 +128,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetPeopleFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetGenresFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetGenresFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetGenresFromShow(showID, where, new Sort(sort), limit); + + Task> GetGenresFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetGenresFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetGenresFromShow(showSlug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index d72908d2..1034c947 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -195,7 +195,29 @@ namespace Kyoo.Controllers } public interface ILibraryRepository : IRepository {} public interface ICollectionRepository : IRepository {} - public interface IGenreRepository : IRepository {} + + public interface IGenreRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } public interface IStudioRepository : IRepository {} public interface IPeopleRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index b46028ee..c0968952 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -271,6 +271,22 @@ namespace Kyoo.Controllers return PeopleRepository.GetFromShow(showSlug, where, sort, limit); } + public Task> GetGenresFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return GenreRepository.GetFromShow(showID, where, sort, limit); + } + + public Task> GetGenresFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return GenreRepository.GetFromShow(showSlug, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index dc891283..982fad76 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -6,18 +6,21 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { public class GenreRepository : LocalRepository, IGenreRepository { private readonly DatabaseContext _database; + private readonly Lazy _shows; protected override Expression> DefaultSort => x => x.Slug; - public GenreRepository(DatabaseContext database) : base(database) + public GenreRepository(DatabaseContext database, IServiceProvider services) : base(database) { _database = database; + _shows = new Lazy(services.GetRequiredService); } @@ -68,5 +71,35 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection genres = await ApplyFilters(_database.GenreLinks.Where(x => x.ShowID == showID) + .Select(x => x.Genre), + where, + sort, + limit); + if (!genres.Any() && await _shows.Value.Get(showID) == null) + throw new ItemNotFound(); + return genres; + } + + public async Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection genres = await ApplyFilters(_database.GenreLinks.Where(x => x.Show.Slug == showSlug) + .Select(x => x.Genre), + where, + sort, + limit); + if (!genres.Any() && await _shows.Value.Get(showSlug) == null) + throw new ItemNotFound(); + return genres; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index f04973df..29c76071 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -220,5 +220,71 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{showID:int}/genre")] + [HttpGet("{showID:int}/genres")] + [Authorize(Policy = "Read")] + public async Task>> GetGenres(int showID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetGenresFromShow(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/genre")] + [HttpGet("{slug}/genres")] + [Authorize(Policy = "Read")] + public async Task>> GetGenre(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetGenresFromShow(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } } } From 1f53affafa497a370ab4d83254f8fe31506c980f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 25 Jul 2020 04:49:48 +0200 Subject: [PATCH 21/36] Adding the /show/studio --- Kyoo.Common/Controllers/ILibraryManager.cs | 3 ++ Kyoo.Common/Controllers/IRepository.cs | 7 ++++- .../Implementations/LibraryManager.cs | 12 +++++++- .../Repositories/StudioRepository.cs | 22 +++++++++++++++ Kyoo/Startup.cs | 2 +- Kyoo/Views/API/ShowsApi.cs | 28 +++++++++++++++++++ 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index aac86eff..f6141aa8 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -148,6 +148,9 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetGenresFromShow(showSlug, where, new Sort(sort), limit); + + Task GetStudioFromShow(int showID); + Task GetStudioFromShow(string showSlug); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 1034c947..4e2fa8c7 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -218,7 +218,12 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); } - public interface IStudioRepository : IRepository {} + + public interface IStudioRepository : IRepository + { + Task GetFromShow(int showID); + Task GetFromShow(string showSlug); + } public interface IPeopleRepository : IRepository { diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index c0968952..c707f9b0 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -286,7 +286,17 @@ namespace Kyoo.Controllers { return GenreRepository.GetFromShow(showSlug, where, sort, limit); } - + + public Task GetStudioFromShow(int showID) + { + return StudioRepository.GetFromShow(showID); + } + + public Task GetStudioFromShow(string showSlug) + { + return StudioRepository.GetFromShow(showSlug); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 299eb712..65ec054e 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -66,5 +66,27 @@ namespace Kyoo.Controllers show.StudioID = null; await _database.SaveChangesAsync(); } + + public async Task GetFromShow(int showID) + { + Studio studio = await _database.Shows + .Where(x => x.ID == showID) + .Select(x => x.Studio) + .FirstOrDefaultAsync(); + if (studio == null && !_database.Shows.Any(x => x.ID == showID)) + throw new ItemNotFound(); + return studio; + } + + public async Task GetFromShow(string showSlug) + { + Studio studio = await _database.Shows + .Where(x => x.Slug == showSlug) + .Select(x => x.Studio) + .FirstOrDefaultAsync(); + if (studio == null && !_database.Shows.Any(x => x.Slug == showSlug)) + throw new ItemNotFound(); + return studio; + } } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index b0c08eb5..ef540054 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -48,7 +48,7 @@ namespace Kyoo options.UseLazyLoadingProxies() .UseNpgsql(_configuration.GetConnectionString("Database")); // .EnableSensitiveDataLogging() - //.UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index 29c76071..84e77c0a 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -286,5 +286,33 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{showID:int}/studio")] + [Authorize(Policy = "Read")] + public async Task> GetStudio(int showID) + { + try + { + return await _libraryManager.GetStudioFromShow(showID); + } + catch (ItemNotFound) + { + return NotFound(); + } + } + + [HttpGet("{slug}/studio")] + [Authorize(Policy = "Read")] + public async Task> GetStudio(string slug) + { + try + { + return await _libraryManager.GetStudioFromShow(slug); + } + catch (ItemNotFound) + { + return NotFound(); + } + } } } From 193004a3eecb19d4b157970ce5fdec362b9c9048 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 25 Jul 2020 17:14:53 +0200 Subject: [PATCH 22/36] Finishing the show api --- Kyoo.Common/Controllers/ILibraryManager.cs | 40 ++++++ Kyoo.Common/Controllers/IRepository.cs | 48 ++++++- .../Implementations/LibraryManager.cs | 32 +++++ .../Repositories/CollectionRepository.cs | 53 ++++++- .../Repositories/GenreRepository.cs | 18 ++- .../Repositories/LibraryRepository.cs | 45 +++++- Kyoo/Views/API/ShowsApi.cs | 132 ++++++++++++++++++ 7 files changed, 359 insertions(+), 9 deletions(-) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index f6141aa8..129db913 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -151,6 +151,46 @@ namespace Kyoo.Controllers Task GetStudioFromShow(int showID); Task GetStudioFromShow(string showSlug); + + Task> GetLibrariesFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetLibrariesFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetLibrariesFromShow(showID, where, new Sort(sort), limit); + + Task> GetLibrariesFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetLibrariesFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetLibrariesFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetCollectionsFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetCollectionsFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetCollectionsFromShow(showID, where, new Sort(sort), limit); + + Task> GetCollectionsFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetCollectionsFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetCollectionsFromShow(showSlug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 4e2fa8c7..c273a89a 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -193,8 +193,52 @@ namespace Kyoo.Controllers { Task Get(int episodeID, string languageTag, bool isForced); } - public interface ILibraryRepository : IRepository {} - public interface ICollectionRepository : IRepository {} + + public interface ILibraryRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } + + public interface ICollectionRepository : IRepository + { + Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showID, where, new Sort(sort), limit); + + Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromShow(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromShow(showSlug, where, new Sort(sort), limit); + } public interface IGenreRepository : IRepository { diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index c707f9b0..0148b953 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -297,6 +297,38 @@ namespace Kyoo.Controllers return StudioRepository.GetFromShow(showSlug); } + public Task> GetLibrariesFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryRepository.GetFromShow(showID, where, sort, limit); + } + + public Task> GetLibrariesFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryRepository.GetFromShow(showSlug, where, sort, limit); + } + + public Task> GetCollectionsFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return CollectionRepository.GetFromShow(showID, where, sort, limit); + } + + public Task> GetCollectionsFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return CollectionRepository.GetFromShow(showSlug, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 72605d39..1d66634a 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -6,19 +6,36 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { public class CollectionRepository : LocalRepository, ICollectionRepository { private readonly DatabaseContext _database; + private readonly Lazy _shows; protected override Expression> DefaultSort => x => x.Name; - public CollectionRepository(DatabaseContext database) : base(database) + public CollectionRepository(DatabaseContext database, IServiceProvider services) : base(database) { _database = database; + _shows = new Lazy(services.GetRequiredService); } - + + public override void Dispose() + { + base.Dispose(); + if (_shows.IsValueCreated) + _shows.Value.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + await _database.DisposeAsync(); + if (_shows.IsValueCreated) + await _shows.Value.DisposeAsync(); + } + public override async Task> Search(string query) { return await _database.Collections @@ -68,5 +85,37 @@ namespace Kyoo.Controllers _database.Entry(link).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection collections = await ApplyFilters(_database.CollectionLinks + .Where(x => x.ShowID == showID) + .Select(x => x.Collection), + where, + sort, + limit); + if (!collections.Any() & await _shows.Value.Get(showID) == null) + throw new ItemNotFound(); + return collections; + } + + public async Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection collections = await ApplyFilters(_database.CollectionLinks + .Where(x => x.Show.Slug == showSlug) + .Select(x => x.Collection), + where, + sort, + limit); + if (!collections.Any() & await _shows.Value.Get(showSlug) == null) + throw new ItemNotFound(); + return collections; + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index 982fad76..9f068e68 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -22,7 +22,20 @@ namespace Kyoo.Controllers _database = database; _shows = new Lazy(services.GetRequiredService); } - + + public override void Dispose() + { + base.Dispose(); + if (_shows.IsValueCreated) + _shows.Value.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + await _database.DisposeAsync(); + if (_shows.IsValueCreated) + await _shows.Value.DisposeAsync(); + } public override async Task> Search(string query) { @@ -92,7 +105,8 @@ namespace Kyoo.Controllers Sort sort = default, Pagination limit = default) { - ICollection genres = await ApplyFilters(_database.GenreLinks.Where(x => x.Show.Slug == showSlug) + ICollection genres = await ApplyFilters(_database.GenreLinks + .Where(x => x.Show.Slug == showSlug) .Select(x => x.Genre), where, sort, diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 7766fe8e..43732277 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { @@ -13,26 +14,32 @@ namespace Kyoo.Controllers { private readonly DatabaseContext _database; private readonly IProviderRepository _providers; + private readonly Lazy _shows; protected override Expression> DefaultSort => x => x.ID; - public LibraryRepository(DatabaseContext database, IProviderRepository providers) : base(database) + public LibraryRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) + : base(database) { _database = database; _providers = providers; + _shows = new Lazy(services.GetRequiredService); } - - + public override void Dispose() { _database.Dispose(); _providers.Dispose(); + if (_shows.IsValueCreated) + _shows.Value.Dispose(); } public override async ValueTask DisposeAsync() { await _database.DisposeAsync(); await _providers.DisposeAsync(); + if (_shows.IsValueCreated) + await _shows.Value.DisposeAsync(); } public override async Task> Search(string query) @@ -90,5 +97,37 @@ namespace Kyoo.Controllers _database.Entry(entry).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromShow(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection libraries = await ApplyFilters(_database.LibraryLinks + .Where(x => x.ShowID == showID) + .Select(x => x.Library), + where, + sort, + limit); + if (!libraries.Any() && await _shows.Value.Get(showID) == null) + throw new ItemNotFound(); + return libraries; + } + + public async Task> GetFromShow(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection libraries = await ApplyFilters(_database.LibraryLinks + .Where(x => x.Show.Slug == showSlug) + .Select(x => x.Library), + where, + sort, + limit); + if (!libraries.Any() && await _shows.Value.Get(showSlug) == null) + throw new ItemNotFound(); + return libraries; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index 84e77c0a..012b1931 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -314,5 +314,137 @@ namespace Kyoo.Api return NotFound(); } } + + [HttpGet("{showID:int}/library")] + [HttpGet("{showID:int}/libraries")] + [Authorize(Policy = "Read")] + public async Task>> GetLibraries(int showID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetLibrariesFromShow(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/library")] + [HttpGet("{slug}/libraries")] + [Authorize(Policy = "Read")] + public async Task>> GetLibraries(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetLibrariesFromShow(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showID:int}/collection")] + [HttpGet("{showID:int}/collections")] + [Authorize(Policy = "Read")] + public async Task>> GetCollections(int showID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("showID"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetCollectionsFromShow(showID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/collection")] + [HttpGet("{slug}/collections")] + [Authorize(Policy = "Read")] + public async Task>> GetCollections(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetCollectionsFromShow(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } } } From 6d2929807319f7e31c12633bde1ac86cb83579c0 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 27 Jul 2020 17:48:47 +0200 Subject: [PATCH 23/36] Starting the library crud api & fixing the regex --- Kyoo.Common/Controllers/ILibraryManager.cs | 40 +++ Kyoo.Common/Controllers/IRepository.cs | 60 +++++ .../Implementations/LibraryManager.cs | 32 +++ Kyoo.CommonAPI/CrudApi.cs | 40 +-- .../Repositories/CollectionRepository.cs | 40 +++ .../Repositories/LibraryRepository.cs | 7 + .../Repositories/ShowRepository.cs | 44 ++++ Kyoo/Views/API/LibrariesAPI.cs | 63 ----- Kyoo/Views/API/LibrariesApi.cs | 236 ++++++++++++++++++ Kyoo/Views/API/ShowsApi.cs | 3 +- Kyoo/appsettings.json | 2 +- 11 files changed, 482 insertions(+), 85 deletions(-) delete mode 100644 Kyoo/Views/API/LibrariesAPI.cs create mode 100644 Kyoo/Views/API/LibrariesApi.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 129db913..c627d715 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -191,6 +191,46 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetCollectionsFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetShowsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetShowsFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetShowsFromLibrary(id, where, new Sort(sort), limit); + + Task> GetShowsFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetShowsFromLibrary(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetShowsFromLibrary(slug, where, new Sort(sort), limit); + + Task> GetCollectionsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetCollectionsFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetCollectionsFromLibrary(id, where, new Sort(sort), limit); + + Task> GetCollectionsFromLibrary(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetCollectionsFromLibrary(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetCollectionsFromLibrary(showSlug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index c273a89a..4cde8d27 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -102,6 +102,46 @@ namespace Kyoo.Controllers public interface IShowRepository : IRepository { Task AddShowLink(int showID, int? libraryID, int? collectionID); + + Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(id, where, new Sort(sort), limit); + + Task> GetFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromLibrary(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(slug, where, new Sort(sort), limit); + + // Task> GetFromCollection(int id, + // Expression> where = null, + // Sort sort = default, + // Pagination limit = default); + // Task> GetFromCollection(int id, + // [Optional] Expression> where, + // Expression> sort, + // Pagination limit = default + // ) => GetFromCollection(id, where, new Sort(sort), limit); + // + // Task> GetFromCollection(string slug, + // Expression> where = null, + // Sort sort = default, + // Pagination limit = default); + // Task> GetFromCollection(string slug, + // [Optional] Expression> where, + // Expression> sort, + // Pagination limit = default + // ) => GetFromCollection(slug, where, new Sort(sort), limit); } public interface ISeasonRepository : IRepository @@ -238,6 +278,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(id, where, new Sort(sort), limit); + + Task> GetFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromLibrary(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(slug, where, new Sort(sort), limit); } public interface IGenreRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 0148b953..d9ffc4d0 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -329,6 +329,38 @@ namespace Kyoo.Controllers return CollectionRepository.GetFromShow(showSlug, where, sort, limit); } + public Task> GetShowsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ShowRepository.GetFromLibrary(id, where, sort, limit); + } + + public Task> GetShowsFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ShowRepository.GetFromLibrary(slug, where, sort, limit); + } + + public Task> GetCollectionsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return CollectionRepository.GetFromLibrary(id, where, sort, limit); + } + + public Task> GetCollectionsFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return CollectionRepository.GetFromLibrary(slug, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 55d1ae33..8cca2f79 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models; using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Configuration; namespace Kyoo.CommonApi @@ -29,7 +26,7 @@ namespace Kyoo.CommonApi [HttpGet("{id:int}")] [Authorize(Policy = "Read")] [JsonDetailed] - public async Task> Get(int id) + public virtual async Task> Get(int id) { T ressource = await _repository.Get(id); if (ressource == null) @@ -41,7 +38,7 @@ namespace Kyoo.CommonApi [HttpGet("{slug}")] [Authorize(Policy = "Read")] [JsonDetailed] - public async Task> Get(string slug) + public virtual async Task> Get(string slug) { T ressource = await _repository.Get(slug); if (ressource == null) @@ -52,7 +49,7 @@ namespace Kyoo.CommonApi [HttpGet] [Authorize(Policy = "Read")] - public async Task>> GetAll([FromQuery] string sortBy, + public virtual async Task>> GetAll([FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, [FromQuery] int limit = 20) @@ -86,12 +83,16 @@ namespace Kyoo.CommonApi [HttpPost] [Authorize(Policy = "Write")] - public async Task> Create([FromBody] T ressource) + public virtual async Task> Create([FromBody] T ressource) { try { return await _repository.Create(ressource); } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } catch (DuplicatedItemException) { T existing = await _repository.Get(ressource.Slug); @@ -101,21 +102,22 @@ namespace Kyoo.CommonApi [HttpPut] [Authorize(Policy = "Write")] - public async Task> Edit([FromQuery] bool resetOld, [FromBody] T ressource) + public virtual async Task> Edit([FromQuery] bool resetOld, [FromBody] T ressource) { - if (ressource.ID <= 0) - { - T old = await _repository.Get(ressource.Slug); - if (old == null) - return NotFound(); - ressource.ID = old.ID; - } + if (ressource.ID > 0) + return await _repository.Edit(ressource, resetOld); + + T old = await _repository.Get(ressource.Slug); + if (old == null) + return NotFound(); + + ressource.ID = old.ID; return await _repository.Edit(ressource, resetOld); } [HttpPut("{id:int}")] [Authorize(Policy = "Write")] - public async Task> Edit(int id, [FromQuery] bool resetOld, [FromBody] T ressource) + public virtual async Task> Edit(int id, [FromQuery] bool resetOld, [FromBody] T ressource) { ressource.ID = id; try @@ -130,7 +132,7 @@ namespace Kyoo.CommonApi [HttpPut("{slug}")] [Authorize(Policy = "Write")] - public async Task> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T ressource) + public virtual async Task> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T ressource) { T old = await _repository.Get(slug); if (old == null) @@ -141,7 +143,7 @@ namespace Kyoo.CommonApi [HttpDelete("{id:int}")] [Authorize(Policy = "Write")] - public async Task Delete(int id) + public virtual async Task Delete(int id) { try { @@ -157,7 +159,7 @@ namespace Kyoo.CommonApi [HttpDelete("{slug}")] [Authorize(Policy = "Write")] - public async Task Delete(string slug) + public virtual async Task Delete(string slug) { try { diff --git a/Kyoo/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 1d66634a..84ddc900 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -14,12 +14,14 @@ namespace Kyoo.Controllers { private readonly DatabaseContext _database; private readonly Lazy _shows; + private readonly Lazy _libraries; protected override Expression> DefaultSort => x => x.Name; public CollectionRepository(DatabaseContext database, IServiceProvider services) : base(database) { _database = database; _shows = new Lazy(services.GetRequiredService); + _libraries = new Lazy(services.GetRequiredService); } public override void Dispose() @@ -27,6 +29,8 @@ namespace Kyoo.Controllers base.Dispose(); if (_shows.IsValueCreated) _shows.Value.Dispose(); + if (_libraries.IsValueCreated) + _libraries.Value.Dispose(); } public override async ValueTask DisposeAsync() @@ -34,6 +38,8 @@ namespace Kyoo.Controllers await _database.DisposeAsync(); if (_shows.IsValueCreated) await _shows.Value.DisposeAsync(); + if (_libraries.IsValueCreated) + await _libraries.Value.DisposeAsync(); } public override async Task> Search(string query) @@ -117,5 +123,39 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return collections; } + + public async Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection collections = await ApplyFilters(_database.LibraryLinks + .Where(x => x.LibraryID == id && x.CollectionID != null) + .Select(x => x.Collection), + where, + sort, + limit); + if (!collections.Any() && await _libraries.Value.Get(id) == null) + throw new ItemNotFound(); + return collections; + } + + public async Task> GetFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection collections = await ApplyFilters(_database.LibraryLinks + .Where(x => x.Library.Slug == slug && x.CollectionID != null) + .Select(x => x.Collection), + where, + sort, + limit); + if (!collections.Any() && await _libraries.Value.Get(slug) == null) + throw new ItemNotFound(); + return collections; + } } + + } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 43732277..5df3c071 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -78,6 +78,13 @@ namespace Kyoo.Controllers protected override async Task Validate(Library obj) { + if (string.IsNullOrEmpty(obj.Slug)) + throw new ArgumentException("The library's slug must be set and not empty"); + if (string.IsNullOrEmpty(obj.Name)) + throw new ArgumentException("The library's name must be set and not empty"); + if (obj.Paths == null || !obj.Paths.Any()) + throw new ArgumentException("The library should have a least one path."); + if (obj.ProviderLinks != null) foreach (ProviderLink link in obj.ProviderLinks) link.Provider = await _providers.CreateIfNotExists(link.Provider); diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 5d9f3af8..2c0e1a16 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -19,6 +19,8 @@ namespace Kyoo.Controllers private readonly IProviderRepository _providers; private readonly Lazy _seasons; private readonly Lazy _episodes; + private readonly Lazy _libraries; + private readonly Lazy _collections; protected override Expression> DefaultSort => x => x.Title; public ShowRepository(DatabaseContext database, @@ -36,6 +38,8 @@ namespace Kyoo.Controllers _providers = providers; _seasons = new Lazy(services.GetRequiredService); _episodes = new Lazy(services.GetRequiredService); + _libraries = new Lazy(services.GetRequiredService); + _collections = new Lazy(services.GetRequiredService); } public override void Dispose() @@ -49,6 +53,10 @@ namespace Kyoo.Controllers _seasons.Value.Dispose(); if (_episodes.IsValueCreated) _episodes.Value.Dispose(); + if (_libraries.IsValueCreated) + _libraries.Value.Dispose(); + if (_collections.IsValueCreated) + _collections.Value.Dispose(); } public override async ValueTask DisposeAsync() @@ -62,6 +70,10 @@ namespace Kyoo.Controllers await _seasons.Value.DisposeAsync(); if (_episodes.IsValueCreated) await _episodes.Value.DisposeAsync(); + if (_libraries.IsValueCreated) + await _libraries.Value.DisposeAsync(); + if (_collections.IsValueCreated) + await _collections.Value.DisposeAsync(); } public override async Task> Search(string query) @@ -181,5 +193,37 @@ namespace Kyoo.Controllers if (obj.Episodes != null) await _episodes.Value.DeleteRange(obj.Episodes); } + + public async Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection shows = await ApplyFilters(_database.LibraryLinks + .Where(x => x.LibraryID == id && x.ShowID != null) + .Select(x => x.Show), + where, + sort, + limit); + if (!shows.Any() && await _libraries.Value.Get(id) == null) + throw new ItemNotFound(); + return shows; + } + + public async Task> GetFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection shows = await ApplyFilters(_database.LibraryLinks + .Where(x => x.Library.Slug == slug && x.ShowID != null) + .Select(x => x.Show), + where, + sort, + limit); + if (!shows.Any() && await _libraries.Value.Get(slug) == null) + throw new ItemNotFound(); + return shows; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/LibrariesAPI.cs b/Kyoo/Views/API/LibrariesAPI.cs deleted file mode 100644 index eeeee468..00000000 --- a/Kyoo/Views/API/LibrariesAPI.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Kyoo.Controllers; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; - -namespace Kyoo.Api -{ - [Route("api/libraries")] - [Route("api/library")] - [ApiController] - public class LibrariesAPI : ControllerBase - { - private readonly ILibraryManager _libraryManager; - private readonly ITaskManager _taskManager; - - public LibrariesAPI(ILibraryManager libraryManager, ITaskManager taskManager) - { - _libraryManager = libraryManager; - _taskManager = taskManager; - } - - [HttpGet] - public async Task> GetLibraries() - { - return await _libraryManager.GetLibraries(); - } - - [Route("/api/library/create")] - [HttpPost] - [Authorize(Policy="Admin")] - public async Task CreateLibrary([FromBody] Library library) - { - if (!ModelState.IsValid) - return BadRequest(library); - if (string.IsNullOrEmpty(library.Slug)) - return BadRequest(new {error = "The library's slug must be set and not empty"}); - if (string.IsNullOrEmpty(library.Name)) - return BadRequest(new {error = "The library's name must be set and not empty"}); - if (library.Paths == null || !library.Paths.Any()) - return BadRequest(new {error = "The library should have a least one path."}); - if (await _libraryManager.GetLibrary(library.Slug) != null) - return BadRequest(new {error = "Duplicated library slug"}); - await _libraryManager.RegisterLibrary(library); - _taskManager.StartTask("scan", library.Slug); - return Ok(); - } - - [HttpGet("{librarySlug}")] - [Authorize(Policy="Read")] - public async Task>> GetShows(string librarySlug) - { - Library library = await _libraryManager.GetLibrary(librarySlug); - - if (library == null) - return NotFound(); - - return library.Shows.Concat(library.Collections.Select(x => x.AsShow())).ToList(); - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/LibrariesApi.cs b/Kyoo/Views/API/LibrariesApi.cs new file mode 100644 index 00000000..055df5b9 --- /dev/null +++ b/Kyoo/Views/API/LibrariesApi.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/library")] + [Route("api/libraries")] + [ApiController] + public class LibrariesAPI : CrudApi + { + private readonly ILibraryManager _libraryManager; + private readonly ITaskManager _taskManager; + + public LibrariesAPI(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) + : base(libraryManager.LibraryRepository, configuration) + { + _libraryManager = libraryManager; + _taskManager = taskManager; + } + + [Authorize(Policy = "Admin")] + public override async Task> Create(Library ressource) + { + ActionResult result = await base.Create(ressource); + if (result.Value != null) + _taskManager.StartTask("scan", result.Value.Slug); + return result; + } + + [HttpGet("{id:int}/show")] + [HttpGet("{id:int}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("id"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShowsFromLibrary(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/show")] + [HttpGet("{slug}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShowsFromLibrary(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{id:int}/collection")] + [HttpGet("{id:int}/collections")] + [Authorize(Policy = "Read")] + public async Task>> GetCollections(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("id"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetCollectionsFromLibrary(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/collection")] + [HttpGet("{slug}/collections")] + [Authorize(Policy = "Read")] + public async Task>> GetCollections(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("slug"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetCollectionsFromLibrary(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + // [HttpGet("{id:int}/item")] + // [HttpGet("{id:int}/items")] + // [Authorize(Policy = "Read")] + // public async Task>> GetItems(int id, + // [FromQuery] string sortBy, + // [FromQuery] int afterID, + // [FromQuery] Dictionary where, + // [FromQuery] int limit = 50) + // { + // where.Remove("id"); + // where.Remove("sortBy"); + // where.Remove("limit"); + // where.Remove("afterID"); + // + // try + // { + // ICollection ressources = await _libraryManager.GetItemsFromLibrary(id, + // ApiHelper.ParseWhere(where), + // new Sort(sortBy), + // new Pagination(limit, afterID)); + // + // return Page(ressources, limit); + // } + // catch (ItemNotFound) + // { + // return NotFound(); + // } + // catch (ArgumentException ex) + // { + // return BadRequest(new {Error = ex.Message}); + // } + // } + // + // [HttpGet("{slug}/collection")] + // [HttpGet("{slug}/collections")] + // [Authorize(Policy = "Read")] + // public async Task>> GetCollections(string slug, + // [FromQuery] string sortBy, + // [FromQuery] int afterID, + // [FromQuery] Dictionary where, + // [FromQuery] int limit = 20) + // { + // where.Remove("slug"); + // where.Remove("sortBy"); + // where.Remove("limit"); + // where.Remove("afterID"); + // + // try + // { + // ICollection ressources = await _libraryManager.GetCollectionsFromLibrary(slug, + // ApiHelper.ParseWhere(where), + // new Sort(sortBy), + // new Pagination(limit, afterID)); + // + // return Page(ressources, limit); + // } + // catch (ItemNotFound) + // { + // return NotFound(); + // } + // catch (ArgumentException ex) + // { + // return BadRequest(new {Error = ex.Message}); + // } + // } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index 012b1931..bda73f63 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -18,8 +18,7 @@ namespace Kyoo.Api { private readonly ILibraryManager _libraryManager; - public ShowsApi(ILibraryManager libraryManager, - IConfiguration configuration) + public ShowsApi(ILibraryManager libraryManager, IConfiguration configuration) : base(libraryManager.ShowRepository, configuration) { _libraryManager = libraryManager; diff --git a/Kyoo/appsettings.json b/Kyoo/appsettings.json index 10101c80..6463ba2a 100644 --- a/Kyoo/appsettings.json +++ b/Kyoo/appsettings.json @@ -33,5 +33,5 @@ "plugins": "plugins/", "defaultPermissions": "read,play,write,admin", "newUserPermissions": "read,play,write,admin", - "regex": "(?:\\/(?.*?))?\\/(?.*)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$" + "regex": "(?:\\/(?.*?))?\\/(?.*?)(?: \\(\\d+\\))?\\/\\k(?: \\(\\d+\\))?(?:(?: S(?\\d+)E(?\\d+))| (?\\d+))?.*$" } \ No newline at end of file From e69eda8df3200a9d73eff0e6f42458c3d34e5f74 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 28 Jul 2020 18:33:10 +0200 Subject: [PATCH 24/36] Adding library items --- Kyoo.Common/Models/LibraryItem.cs | 84 +++++++++++++++++++ Kyoo.Common/Models/Ressources/Collection.cs | 13 +-- Kyoo.Common/Models/Ressources/Show.cs | 5 +- .../Repositories/LibraryRepository.cs | 24 ++++++ Kyoo/Startup.cs | 6 +- Kyoo/Views/API/LibrariesApi.cs | 64 +++++++------- 6 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 Kyoo.Common/Models/LibraryItem.cs diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs new file mode 100644 index 00000000..176c056f --- /dev/null +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -0,0 +1,84 @@ +using System; +using System.Linq.Expressions; + +namespace Kyoo.Models +{ + public enum ItemType + { + Show, + Movie, + Collection + } + + public class LibraryItem : IRessource + { + public int ID { get; set; } + public string Slug { get; set; } + public string Title { get; set; } + public string Overview { get; set; } + public Status? Status { get; set; } + public string TrailerUrl { get; set; } + public int? StartYear { get; set; } + public int? EndYear { get; set; } + public string Poster { get; set; } + public ItemType Type { get; set; } + + public LibraryItem() {} + + public LibraryItem(Show show) + { + ID = show.ID; + Slug = show.Slug; + Title = show.Title; + Overview = show.Overview; + Status = show.Status; + TrailerUrl = show.TrailerUrl; + StartYear = show.StartYear; + EndYear = show.EndYear; + Poster = show.Poster; + Type = show.IsMovie ? ItemType.Movie : ItemType.Show; + } + + public LibraryItem(Collection collection) + { + ID = -collection.ID; + Slug = collection.Slug; + Title = collection.Name; + Overview = collection.Overview; + Status = Models.Status.Unknown; + TrailerUrl = null; + StartYear = null; + EndYear = null; + Poster = collection.Poster; + Type = ItemType.Collection; + } + + public static Expression> FromShow => x => new LibraryItem + { + ID = x.ID, + Slug = x.Slug, + Title = x.Title, + Overview = x.Overview, + Status = x.Status, + TrailerUrl = x.TrailerUrl, + StartYear = x.StartYear, + EndYear = x.EndYear, + Poster= x.Poster, + Type = x.IsMovie ? ItemType.Movie : ItemType.Show + }; + + public static Expression> FromCollection => x => new LibraryItem + { + ID = -x.ID, + Slug = x.Slug, + Title = x.Name, + Overview = x.Overview, + Status = Models.Status.Unknown, + TrailerUrl = null, + StartYear = null, + EndYear = null, + Poster= x.Poster, + Type = ItemType.Collection + }; + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Ressources/Collection.cs b/Kyoo.Common/Models/Ressources/Collection.cs index a32478f4..e6fcee84 100644 --- a/Kyoo.Common/Models/Ressources/Collection.cs +++ b/Kyoo.Common/Models/Ressources/Collection.cs @@ -12,7 +12,6 @@ namespace Kyoo.Models public string Name { get; set; } public string Poster { get; set; } public string Overview { get; set; } - [JsonIgnore] public string ImgPrimary { get; set; } [NotMergable] [JsonIgnore] public virtual IEnumerable Links { get; set; } public virtual IEnumerable Shows { @@ -30,20 +29,12 @@ namespace Kyoo.Models public Collection() { } - public Collection(string slug, string name, string overview, string imgPrimary) + public Collection(string slug, string name, string overview, string poster) { Slug = slug; Name = name; Overview = overview; - ImgPrimary = imgPrimary; - } - - public Show AsShow() - { - return new Show(Slug, Name, null, null, Overview, null, null, null, null, null, null) - { - IsCollection = true - }; + Poster = poster; } } } diff --git a/Kyoo.Common/Models/Ressources/Show.cs b/Kyoo.Common/Models/Ressources/Show.cs index 5b71b7a4..3a129b65 100644 --- a/Kyoo.Common/Models/Ressources/Show.cs +++ b/Kyoo.Common/Models/Ressources/Show.cs @@ -28,7 +28,6 @@ namespace Kyoo.Models public bool IsMovie { get; set; } - public bool IsCollection; public virtual IEnumerable Genres { @@ -82,7 +81,6 @@ namespace Kyoo.Models StartYear = startYear; EndYear = endYear; ExternalIDs = externalIDs; - IsCollection = false; } public Show(string slug, @@ -112,7 +110,6 @@ namespace Kyoo.Models Logo = logo; Backdrop = backdrop; ExternalIDs = externalIDs; - IsCollection = false; } public string GetID(string provider) @@ -140,5 +137,5 @@ namespace Kyoo.Models } } - public enum Status { Finished, Airing, Planned } + public enum Status { Finished, Airing, Planned, Unknown } } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 5df3c071..e4718b76 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -136,5 +136,29 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return libraries; } + + public async Task> GetItems(string librarySlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + IQueryable query = _database.Shows + .Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) + .Select(LibraryItem.FromShow) + .Concat(_database.Collections + .Select(LibraryItem.FromCollection)); + + ICollection items = await ApplyFilters(query, + async id => id > 0 + ? new LibraryItem(await _database.Shows.FirstOrDefaultAsync(x => x.ID == id)) + : new LibraryItem(await _database.Collections.FirstOrDefaultAsync(x => x.ID == -id)), + x => x.Slug, + where, + sort, + limit); + if (!items.Any() && await Get(librarySlug) == null) + throw new ItemNotFound(); + return items; + } } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index ef540054..cb160a92 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -46,9 +46,9 @@ namespace Kyoo services.AddDbContext(options => { options.UseLazyLoadingProxies() - .UseNpgsql(_configuration.GetConnectionString("Database")); - // .EnableSensitiveDataLogging() - // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + .UseNpgsql(_configuration.GetConnectionString("Database")) + .EnableSensitiveDataLogging() + .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => diff --git a/Kyoo/Views/API/LibrariesApi.cs b/Kyoo/Views/API/LibrariesApi.cs index 055df5b9..689e71e0 100644 --- a/Kyoo/Views/API/LibrariesApi.cs +++ b/Kyoo/Views/API/LibrariesApi.cs @@ -167,38 +167,38 @@ namespace Kyoo.Api } } - // [HttpGet("{id:int}/item")] - // [HttpGet("{id:int}/items")] - // [Authorize(Policy = "Read")] - // public async Task>> GetItems(int id, - // [FromQuery] string sortBy, - // [FromQuery] int afterID, - // [FromQuery] Dictionary where, - // [FromQuery] int limit = 50) - // { - // where.Remove("id"); - // where.Remove("sortBy"); - // where.Remove("limit"); - // where.Remove("afterID"); - // - // try - // { - // ICollection ressources = await _libraryManager.GetItemsFromLibrary(id, - // ApiHelper.ParseWhere(where), - // new Sort(sortBy), - // new Pagination(limit, afterID)); - // - // return Page(ressources, limit); - // } - // catch (ItemNotFound) - // { - // return NotFound(); - // } - // catch (ArgumentException ex) - // { - // return BadRequest(new {Error = ex.Message}); - // } - // } + [HttpGet("{id:int}/item")] + [HttpGet("{id:int}/items")] + [Authorize(Policy = "Read")] + public async Task>> GetItems(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("id"); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await ((LibraryRepository)_libraryManager.LibraryRepository).GetItems("", + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } // // [HttpGet("{slug}/collection")] // [HttpGet("{slug}/collections")] From 5b0b2fbb7bbe29c3dc9548865c4c857d51215547 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 29 Jul 2020 00:33:58 +0200 Subject: [PATCH 25/36] Finishing the library's items route --- Kyoo.Common/Controllers/ILibraryManager.cs | 23 +++ Kyoo.Common/Controllers/IRepository.cs | 25 ++++ .../Implementations/LibraryManager.cs | 19 +++ .../Models/{ => Ressources}/LibraryItem.cs | 0 Kyoo.CommonAPI/LocalRepository.cs | 2 +- .../Repositories/LibraryItemRepository.cs | 136 ++++++++++++++++++ .../Repositories/LibraryRepository.cs | 24 ---- Kyoo/Startup.cs | 5 +- Kyoo/Views/API/LibrariesApi.cs | 72 +++++----- Kyoo/Views/API/ShowsApi.cs | 12 -- 10 files changed, 240 insertions(+), 78 deletions(-) rename Kyoo.Common/Models/{ => Ressources}/LibraryItem.cs (100%) create mode 100644 Kyoo/Controllers/Repositories/LibraryItemRepository.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index c627d715..52acb660 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -12,6 +12,7 @@ namespace Kyoo.Controllers { // Repositories ILibraryRepository LibraryRepository { get; } + ILibraryItemRepository LibraryItemRepository { get; } ICollectionRepository CollectionRepository { get; } IShowRepository ShowRepository { get; } ISeasonRepository SeasonRepository { get; } @@ -231,6 +232,28 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetCollectionsFromLibrary(showSlug, where, new Sort(sort), limit); + + Task> GetItemsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + + Task> GetItemsFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetItemsFromLibrary(id, where, new Sort(sort), limit); + + Task> GetItemsFromLibrary(string librarySlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + + Task> GetItemsFromLibrary(string librarySlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetItemsFromLibrary(librarySlug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 4cde8d27..cdcc9359 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -257,6 +257,31 @@ namespace Kyoo.Controllers ) => GetFromShow(showSlug, where, new Sort(sort), limit); } + public interface ILibraryItemRepository : IRepository + { + public Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + + public Task> GetFromLibrary(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(id, where, new Sort(sort), limit); + + public Task> GetFromLibrary(string librarySlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + + public Task> GetFromLibrary(string librarySlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromLibrary(librarySlug, where, new Sort(sort), limit); + } + public interface ICollectionRepository : IRepository { Task> GetFromShow(int showID, diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index d9ffc4d0..fd6c2e79 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -9,6 +9,7 @@ namespace Kyoo.Controllers public class LibraryManager : ILibraryManager { public ILibraryRepository LibraryRepository { get; } + public ILibraryItemRepository LibraryItemRepository { get; } public ICollectionRepository CollectionRepository { get; } public IShowRepository ShowRepository { get; } public ISeasonRepository SeasonRepository { get; } @@ -20,6 +21,7 @@ namespace Kyoo.Controllers public IProviderRepository ProviderRepository { get; } public LibraryManager(ILibraryRepository libraryRepository, + ILibraryItemRepository libraryItemRepository, ICollectionRepository collectionRepository, IShowRepository showRepository, ISeasonRepository seasonRepository, @@ -31,6 +33,7 @@ namespace Kyoo.Controllers IPeopleRepository peopleRepository) { LibraryRepository = libraryRepository; + LibraryItemRepository = libraryItemRepository; CollectionRepository = collectionRepository; ShowRepository = showRepository; SeasonRepository = seasonRepository; @@ -361,6 +364,22 @@ namespace Kyoo.Controllers return CollectionRepository.GetFromLibrary(slug, where, sort, limit); } + public Task> GetItemsFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryItemRepository.GetFromLibrary(id, where, sort, limit); + } + + public Task> GetItemsFromLibrary(string librarySlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryItemRepository.GetFromLibrary(librarySlug, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/Ressources/LibraryItem.cs similarity index 100% rename from Kyoo.Common/Models/LibraryItem.cs rename to Kyoo.Common/Models/Ressources/LibraryItem.cs diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 1842efe1..56381b6e 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -45,7 +45,7 @@ namespace Kyoo.Controllers public abstract Task> Search(string query); - public Task> GetAll(Expression> where = null, + public virtual Task> GetAll(Expression> where = null, Sort sort = default, Pagination limit = default) { diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs new file mode 100644 index 00000000..92a029a2 --- /dev/null +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Kyoo.Controllers +{ + public class LibraryItemRepository : LocalRepository, ILibraryItemRepository + { + private readonly DatabaseContext _database; + private readonly IProviderRepository _providers; + private readonly Lazy _libraries; + private readonly Lazy _shows; + private readonly Lazy _collections; + protected override Expression> DefaultSort => x => x.Title; + + + public LibraryItemRepository(DatabaseContext database, IProviderRepository providers, IServiceProvider services) + : base(database) + { + _database = database; + _providers = providers; + _libraries = new Lazy(services.GetRequiredService); + _shows = new Lazy(services.GetRequiredService); + _collections = new Lazy(services.GetRequiredService); + } + + public override void Dispose() + { + _database.Dispose(); + _providers.Dispose(); + if (_shows.IsValueCreated) + _shows.Value.Dispose(); + if (_collections.IsValueCreated) + _collections.Value.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + await _database.DisposeAsync(); + await _providers.DisposeAsync(); + if (_shows.IsValueCreated) + await _shows.Value.DisposeAsync(); + if (_collections.IsValueCreated) + await _collections.Value.DisposeAsync(); + } + + public override async Task Get(int id) + { + return id > 0 + ? new LibraryItem(await _shows.Value.Get(id)) + : new LibraryItem(await _collections.Value.Get(-id)); + } + + public override Task Get(string slug) + { + throw new InvalidOperationException(); + } + + private IQueryable ItemsQuery + => _database.Shows + .Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) + .Select(LibraryItem.FromShow) + .Concat(_database.Collections + .Select(LibraryItem.FromCollection)); + + public override Task> GetAll(Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ApplyFilters(ItemsQuery, where, sort, limit); + } + + public override async Task> Search(string query) + { + return await ItemsQuery + .Where(x => EF.Functions.ILike(x.Title, $"%{query}%")) + .Take(20) + .ToListAsync(); + } + + public override Task Create(LibraryItem obj) => throw new InvalidOperationException(); + public override Task CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException(); + public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); + protected override Task Validate(LibraryItem obj) => throw new InvalidOperationException(); + public override Task Delete(int id) => throw new InvalidOperationException(); + public override Task Delete(string slug) => throw new InvalidOperationException(); + public override Task Delete(LibraryItem obj) => throw new InvalidOperationException(); + + private IQueryable LibraryRelatedQuery(Expression> selector) + => _database.LibraryLinks + .Where(selector) + .Select(x => x.Show) + .Where(x => x != null) + .Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) + .Select(LibraryItem.FromShow) + .Concat(_database.LibraryLinks + .Where(selector) + .Select(x => x.Collection) + .Where(x => x != null) + .Select(LibraryItem.FromCollection)); + + public async Task> GetFromLibrary(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.LibraryID == id), + where, + sort, + limit); + if (!items.Any() && await _libraries.Value.Get(id) == null) + throw new ItemNotFound(); + return items; + } + + public async Task> GetFromLibrary(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection items = await ApplyFilters(LibraryRelatedQuery(x => x.Library.Slug == slug), + where, + sort, + limit); + if (!items.Any() && await _libraries.Value.Get(slug) == null) + throw new ItemNotFound(); + return items; + } + } +} \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index e4718b76..5df3c071 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -136,29 +136,5 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return libraries; } - - public async Task> GetItems(string librarySlug, - Expression> where = null, - Sort sort = default, - Pagination limit = default) - { - IQueryable query = _database.Shows - .Where(x => !_database.CollectionLinks.Any(y => y.ShowID == x.ID)) - .Select(LibraryItem.FromShow) - .Concat(_database.Collections - .Select(LibraryItem.FromCollection)); - - ICollection items = await ApplyFilters(query, - async id => id > 0 - ? new LibraryItem(await _database.Shows.FirstOrDefaultAsync(x => x.ID == id)) - : new LibraryItem(await _database.Collections.FirstOrDefaultAsync(x => x.ID == -id)), - x => x.Slug, - where, - sort, - limit); - if (!items.Any() && await Get(librarySlug) == null) - throw new ItemNotFound(); - return items; - } } } \ No newline at end of file diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index cb160a92..5e695133 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -47,8 +47,8 @@ namespace Kyoo { options.UseLazyLoadingProxies() .UseNpgsql(_configuration.GetConnectionString("Database")) - .EnableSensitiveDataLogging() - .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + .EnableSensitiveDataLogging() + .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => @@ -134,6 +134,7 @@ namespace Kyoo services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/Kyoo/Views/API/LibrariesApi.cs b/Kyoo/Views/API/LibrariesApi.cs index 689e71e0..d7786348 100644 --- a/Kyoo/Views/API/LibrariesApi.cs +++ b/Kyoo/Views/API/LibrariesApi.cs @@ -44,7 +44,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("id"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -77,7 +76,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -110,7 +108,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("id"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -143,7 +140,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -176,14 +172,45 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("id"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); try { - ICollection ressources = await ((LibraryRepository)_libraryManager.LibraryRepository).GetItems("", + ICollection ressources = await _libraryManager.GetItemsFromLibrary(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/item")] + [HttpGet("{slug}/items")] + [Authorize(Policy = "Read")] + public async Task>> GetItems(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetItemsFromLibrary(slug, ApiHelper.ParseWhere(where), new Sort(sortBy), new Pagination(limit, afterID)); @@ -199,38 +226,5 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } - // - // [HttpGet("{slug}/collection")] - // [HttpGet("{slug}/collections")] - // [Authorize(Policy = "Read")] - // public async Task>> GetCollections(string slug, - // [FromQuery] string sortBy, - // [FromQuery] int afterID, - // [FromQuery] Dictionary where, - // [FromQuery] int limit = 20) - // { - // where.Remove("slug"); - // where.Remove("sortBy"); - // where.Remove("limit"); - // where.Remove("afterID"); - // - // try - // { - // ICollection ressources = await _libraryManager.GetCollectionsFromLibrary(slug, - // ApiHelper.ParseWhere(where), - // new Sort(sortBy), - // new Pagination(limit, afterID)); - // - // return Page(ressources, limit); - // } - // catch (ItemNotFound) - // { - // return NotFound(); - // } - // catch (ArgumentException ex) - // { - // return BadRequest(new {Error = ex.Message}); - // } - // } } } \ No newline at end of file diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowsApi.cs index bda73f63..9f987e66 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowsApi.cs @@ -33,7 +33,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -66,7 +65,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 20) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -99,7 +97,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -132,7 +129,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 50) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -164,7 +160,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -196,7 +191,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -229,7 +223,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -262,7 +255,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -323,7 +315,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -356,7 +347,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -389,7 +379,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("showID"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); @@ -422,7 +411,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - where.Remove("slug"); where.Remove("sortBy"); where.Remove("limit"); where.Remove("afterID"); From 24355bac84826a7b06be7c7fcf23d1f7edf7aad8 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 29 Jul 2020 00:46:24 +0200 Subject: [PATCH 26/36] Adding the /items route --- Kyoo/Startup.cs | 6 +-- Kyoo/Views/API/LibraryItemsApi.cs | 63 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 Kyoo/Views/API/LibraryItemsApi.cs diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 5e695133..d6ff4004 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -46,9 +46,9 @@ namespace Kyoo services.AddDbContext(options => { options.UseLazyLoadingProxies() - .UseNpgsql(_configuration.GetConnectionString("Database")) - .EnableSensitiveDataLogging() - .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); + .UseNpgsql(_configuration.GetConnectionString("Database")); + // .EnableSensitiveDataLogging() + // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); }, ServiceLifetime.Transient); services.AddDbContext(options => diff --git a/Kyoo/Views/API/LibraryItemsApi.cs b/Kyoo/Views/API/LibraryItemsApi.cs new file mode 100644 index 00000000..9a32dade --- /dev/null +++ b/Kyoo/Views/API/LibraryItemsApi.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/item")] + [Route("api/items")] + [ApiController] + public class LibraryItemsApi : ControllerBase + { + private readonly ILibraryItemRepository _libraryItems; + private readonly string _baseURL; + + + public LibraryItemsApi(ILibraryItemRepository libraryItems, IConfiguration configuration) + { + _libraryItems = libraryItems; + _baseURL = configuration.GetValue("public_url").TrimEnd('/'); + } + + [HttpGet] + [Authorize(Policy = "Read")] + public async Task>> GetAll([FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 50) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryItems.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 (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file From d77cae6d2be18695e33eb54e2ad37d12c10b7509 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 1 Aug 2020 18:22:46 +0200 Subject: [PATCH 27/36] Fixing sort limit with native types (non boxed ones) --- Kyoo.Common/Controllers/IRepository.cs | 2 +- .../Models/{Ressources => }/LibraryItem.cs | 2 +- Kyoo.Common/Models/Page.cs | 2 +- Kyoo.Common/Models/PeopleLink.cs | 2 +- .../{Ressources => Resources}/Collection.cs | 2 +- .../{Ressources => Resources}/Episode.cs | 2 +- .../Models/{Ressources => Resources}/Genre.cs | 2 +- .../IRessource.cs => Resources/IResource.cs} | 2 +- .../{Ressources => Resources}/Library.cs | 2 +- .../{Ressources => Resources}/People.cs | 2 +- .../{Ressources => Resources}/ProviderID.cs | 2 +- .../{Ressources => Resources}/Season.cs | 2 +- .../Models/{Ressources => Resources}/Show.cs | 2 +- .../{Ressources => Resources}/Studio.cs | 2 +- .../Models/{Ressources => Resources}/Track.cs | 2 +- Kyoo.CommonAPI/CrudApi.cs | 4 +- Kyoo.CommonAPI/LocalRepository.cs | 9 ++- Kyoo/Tasks/Crawler.cs | 58 +++++++++++++++---- Kyoo/Views/WebClient | 2 +- 19 files changed, 70 insertions(+), 33 deletions(-) rename Kyoo.Common/Models/{Ressources => }/LibraryItem.cs (97%) rename Kyoo.Common/Models/{Ressources => Resources}/Collection.cs (96%) rename Kyoo.Common/Models/{Ressources => Resources}/Episode.cs (98%) rename Kyoo.Common/Models/{Ressources => Resources}/Genre.cs (96%) rename Kyoo.Common/Models/{Ressources/IRessource.cs => Resources/IResource.cs} (76%) rename Kyoo.Common/Models/{Ressources => Resources}/Library.cs (97%) rename Kyoo.Common/Models/{Ressources => Resources}/People.cs (94%) rename Kyoo.Common/Models/{Ressources => Resources}/ProviderID.cs (92%) rename Kyoo.Common/Models/{Ressources => Resources}/Season.cs (96%) rename Kyoo.Common/Models/{Ressources => Resources}/Show.cs (98%) rename Kyoo.Common/Models/{Ressources => Resources}/Studio.cs (94%) rename Kyoo.Common/Models/{Ressources => Resources}/Track.cs (98%) diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index cdcc9359..e2e5a2c0 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -68,7 +68,7 @@ namespace Kyoo.Controllers } } - public interface IRepository : IDisposable, IAsyncDisposable where T : IRessource + public interface IRepository : IDisposable, IAsyncDisposable where T : IResource { Task Get(int id); Task Get(string slug); diff --git a/Kyoo.Common/Models/Ressources/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs similarity index 97% rename from Kyoo.Common/Models/Ressources/LibraryItem.cs rename to Kyoo.Common/Models/LibraryItem.cs index 176c056f..4264a469 100644 --- a/Kyoo.Common/Models/Ressources/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models Collection } - public class LibraryItem : IRessource + public class LibraryItem : IResource { 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 5d4505e7..f91e94d8 100644 --- a/Kyoo.Common/Models/Page.cs +++ b/Kyoo.Common/Models/Page.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Kyoo.Models { - public class Page where T : IRessource + public class Page where T : IResource { public string This { get; set; } public string First { get; set; } diff --git a/Kyoo.Common/Models/PeopleLink.cs b/Kyoo.Common/Models/PeopleLink.cs index d36f90bd..f095d802 100644 --- a/Kyoo.Common/Models/PeopleLink.cs +++ b/Kyoo.Common/Models/PeopleLink.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class PeopleLink : IRessource + public class PeopleLink : IResource { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int PeopleID { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs similarity index 96% rename from Kyoo.Common/Models/Ressources/Collection.cs rename to Kyoo.Common/Models/Resources/Collection.cs index e6fcee84..eb932de2 100644 --- a/Kyoo.Common/Models/Ressources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Collection : IRessource + public class Collection : IResource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs similarity index 98% rename from Kyoo.Common/Models/Ressources/Episode.cs rename to Kyoo.Common/Models/Resources/Episode.cs index c9725fe3..a512ed75 100644 --- a/Kyoo.Common/Models/Ressources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Episode : IRessource, IOnMerge + public class Episode : IResource, IOnMerge { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int ShowID { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs similarity index 96% rename from Kyoo.Common/Models/Ressources/Genre.cs rename to Kyoo.Common/Models/Resources/Genre.cs index fe4a2533..2fe7c3a3 100644 --- a/Kyoo.Common/Models/Ressources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Genre : IRessource + public class Genre : IResource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/IRessource.cs b/Kyoo.Common/Models/Resources/IResource.cs similarity index 76% rename from Kyoo.Common/Models/Ressources/IRessource.cs rename to Kyoo.Common/Models/Resources/IResource.cs index 6f86a4af..7ef6c4df 100644 --- a/Kyoo.Common/Models/Ressources/IRessource.cs +++ b/Kyoo.Common/Models/Resources/IResource.cs @@ -1,6 +1,6 @@ namespace Kyoo.Models { - public interface IRessource + public interface IResource { public int ID { get; set; } public string Slug { get; } diff --git a/Kyoo.Common/Models/Ressources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs similarity index 97% rename from Kyoo.Common/Models/Ressources/Library.cs rename to Kyoo.Common/Models/Resources/Library.cs index 3b192df8..11526f43 100644 --- a/Kyoo.Common/Models/Ressources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Library : IRessource + public class Library : IResource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/People.cs b/Kyoo.Common/Models/Resources/People.cs similarity index 94% rename from Kyoo.Common/Models/Ressources/People.cs rename to Kyoo.Common/Models/Resources/People.cs index 02c967b0..b2bc658e 100644 --- a/Kyoo.Common/Models/Ressources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class People : IRessource + public class People : IResource { public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs similarity index 92% rename from Kyoo.Common/Models/Ressources/ProviderID.cs rename to Kyoo.Common/Models/Resources/ProviderID.cs index 12753fe2..387376fa 100644 --- a/Kyoo.Common/Models/Ressources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class ProviderID : IRessource + public class ProviderID : IResource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs similarity index 96% rename from Kyoo.Common/Models/Ressources/Season.cs rename to Kyoo.Common/Models/Resources/Season.cs index 1307bab3..2bd62a6a 100644 --- a/Kyoo.Common/Models/Ressources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Season : IRessource + public class Season : IResource { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int ShowID { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs similarity index 98% rename from Kyoo.Common/Models/Ressources/Show.cs rename to Kyoo.Common/Models/Resources/Show.cs index 3a129b65..aabfc697 100644 --- a/Kyoo.Common/Models/Ressources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -5,7 +5,7 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { - public class Show : IRessource, IOnMerge + public class Show : IResource, IOnMerge { [JsonIgnore] public int ID { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs similarity index 94% rename from Kyoo.Common/Models/Ressources/Studio.cs rename to Kyoo.Common/Models/Resources/Studio.cs index 02d61c77..8936bf66 100644 --- a/Kyoo.Common/Models/Ressources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; namespace Kyoo.Models { - public class Studio : IRessource + public class Studio : IResource { [JsonIgnore] public int ID { get; set; } public string Slug { get; set; } diff --git a/Kyoo.Common/Models/Ressources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs similarity index 98% rename from Kyoo.Common/Models/Ressources/Track.cs rename to Kyoo.Common/Models/Resources/Track.cs index 61209807..88953eb2 100644 --- a/Kyoo.Common/Models/Ressources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -53,7 +53,7 @@ namespace Kyoo.Models } } - public class Track : Stream, IRessource + public class Track : Stream, IResource { [JsonIgnore] public int ID { get; set; } [JsonIgnore] public int EpisodeID { get; set; } diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index 8cca2f79..d029659e 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Configuration; namespace Kyoo.CommonApi { [ApiController] - public class CrudApi : ControllerBase where T : IRessource + public class CrudApi : ControllerBase where T : IResource { private readonly IRepository _repository; private readonly string _baseURL; @@ -73,7 +73,7 @@ namespace Kyoo.CommonApi } protected Page Page(ICollection ressources, int limit) - where TResult : IRessource + where TResult : IResource { return new Page(ressources, _baseURL + Request.Path, diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 56381b6e..67442f12 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -11,7 +11,7 @@ using Npgsql; namespace Kyoo.Controllers { - public abstract class LocalRepository : IRepository where T : class, IRessource + public abstract class LocalRepository : IRepository where T : class, IResource { private readonly DbContext _database; @@ -76,9 +76,12 @@ namespace Kyoo.Controllers if (limit.AfterID != 0) { TValue after = await get(limit.AfterID); - object afterObj = sortKey.Compile()(after); + Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert + ? ((UnaryExpression)sortKey.Body).Operand + : sortKey.Body; + Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type); query = query.Where(Expression.Lambda>( - ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortKey.Body, Expression.Constant(afterObj)), + ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key), sortKey.Parameters.First() )); } diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index bc3b2eeb..387bd3af 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -309,7 +309,9 @@ namespace Kyoo.Controllers private async Task> GetTracks(Episode episode) { IEnumerable tracks = await _transcoder.GetTrackInfo(episode.Path); - List epTracks = tracks.Where(x => x.Type != StreamType.Subtitle).Concat(GetExtractedSubtitles(episode)).ToList(); + List epTracks = tracks.Where(x => x.Type != StreamType.Subtitle) + .Concat(GetExtractedSubtitles(episode)) + .ToList(); if (epTracks.Count(x => !x.IsExternal) < tracks.Count()) epTracks.AddRange(await _transcoder.ExtractSubtitles(episode.Path)); episode.Tracks = epTracks; @@ -329,26 +331,58 @@ namespace Kyoo.Controllers foreach (string sub in Directory.EnumerateFiles(path, "", SearchOption.AllDirectories)) { string episodeLink = Path.GetFileNameWithoutExtension(episode.Path); - - if (!sub.Contains(episodeLink!)) + string subName = Path.GetFileName(sub); + + if (episodeLink == null + || subName?.Contains(episodeLink) == false + || subName.Length < episodeLink.Length + 5) continue; - string language = sub.Substring(Path.GetDirectoryName(sub).Length + episodeLink.Length + 2, 3); + string language = subName.Substring(episodeLink.Length + 2, 3); bool isDefault = sub.Contains("default"); bool isForced = sub.Contains("forced"); - Track track = new Track(StreamType.Subtitle, null, language, isDefault, isForced, null, false, sub) { EpisodeID = episode.ID }; + Track track = new Track(StreamType.Subtitle, null, language, isDefault, isForced, null, false, sub) + { + EpisodeID = episode.ID, + Codec = Path.GetExtension(sub) switch + { + ".ass" => "ass", + ".srt" => "subrip", + _ => null + } + }; - if (Path.GetExtension(sub) == ".ass") - track.Codec = "ass"; - else if (Path.GetExtension(sub) == ".srt") - track.Codec = "subrip"; - else - track.Codec = null; tracks.Add(track); } return tracks; } - private static readonly string[] VideoExtensions = { ".webm", ".mkv", ".flv", ".vob", ".ogg", ".ogv", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".asf", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".3gp", ".3g2" }; + private static readonly string[] VideoExtensions = + { + ".webm", + ".mkv", + ".flv", + ".vob", + ".ogg", + ".ogv", + ".avi", + ".mts", + ".m2ts", + ".ts", + ".mov", + ".qt", + ".asf", + ".mp4", + ".m4p", + ".m4v", + ".mpg", + ".mp2", + ".mpeg", + ".mpe", + ".mpv", + ".m2v", + ".3gp", + ".3g2" + }; private static bool IsVideo(string filePath) { diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index fffb6690..45afdbb4 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit fffb6690fc5db161767753d1fc554be04eb732d4 +Subproject commit 45afdbb44ed28b5ee408923b6d4d13534e284517 From f561b6044c34d3f8a4f16e2d351dd78d073d8b2a Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Aug 2020 01:52:09 +0200 Subject: [PATCH 28/36] Invaliding enum sort key --- Kyoo.CommonAPI/LocalRepository.cs | 12 ++++++++---- Kyoo/Views/WebClient | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 67442f12..cfc1325a 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -69,16 +69,20 @@ namespace Kyoo.Controllers { if (where != null) query = query.Where(where); - + Expression> sortKey = sort.Key ?? defaultSort; + Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert + ? ((UnaryExpression)sortKey.Body).Operand + : sortKey.Body; + + if (typeof(Enum).IsAssignableFrom(sortExpression.Type)) + throw new ArgumentException("Invalid sort key."); + query = sort.Descendant ? query.OrderByDescending(sortKey) : query.OrderBy(sortKey); if (limit.AfterID != 0) { TValue after = await get(limit.AfterID); - Expression sortExpression = sortKey.Body.NodeType == ExpressionType.Convert - ? ((UnaryExpression)sortKey.Body).Operand - : sortKey.Body; Expression key = Expression.Constant(sortKey.Compile()(after), sortExpression.Type); query = query.Where(Expression.Lambda>( ApiHelper.StringCompatibleExpression(Expression.GreaterThan, sortExpression, key), diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 45afdbb4..d692f9f3 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 45afdbb44ed28b5ee408923b6d4d13534e284517 +Subproject commit d692f9f3d364d7d2748205374a3a84fb1fd4a938 From 9bf18c8c8c902f3f2906e250deaa435f39dece39 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 3 Aug 2020 00:32:16 +0200 Subject: [PATCH 29/36] Adding a rest season api --- Kyoo/Views/API/SeasonApi.cs | 127 ++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Kyoo/Views/API/SeasonApi.cs diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs new file mode 100644 index 00000000..c46f5ce5 --- /dev/null +++ b/Kyoo/Views/API/SeasonApi.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/season")] + [Route("api/seasons")] + [ApiController] + public class SeasonApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public SeasonApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.SeasonRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{seasonID:int}/season")] + [HttpGet("{seasonID:int}/seasons")] + [Authorize(Policy = "Read")] + public async Task>> GetSeasons(int seasonID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetEpisodesFromSeason(seasonID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showSlug}-{seasonNumber:int}/season")] + [HttpGet("{showSlug}-{seasonNumber:int}/seasons")] + [Authorize(Policy = "Read")] + public async Task>> GetSeasons(string showSlug, + int seasonNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetEpisodesFromSeason(showSlug, + seasonNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showID:int}-{seasonNumber:int}/season")] + [HttpGet("{showID:int}-{seasonNumber:int}/seasons")] + [Authorize(Policy = "Read")] + public async Task>> GetSeasons(int showID, + int seasonNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetEpisodesFromSeason(showID, + seasonNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file From 9c549c593b814af477e1c97ca4a885a1863d4a84 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 3 Aug 2020 03:27:09 +0200 Subject: [PATCH 30/36] Cleaning up --- .../Exceptions/DuplicatedItemException.cs | 2 + Kyoo.CommonAPI/LocalRepository.cs | 6 - .../Repositories/CollectionRepository.cs | 14 +-- .../Repositories/EpisodeRepository.cs | 16 +-- .../Repositories/GenreRepository.cs | 15 +-- .../Repositories/LibraryRepository.cs | 13 +- .../Repositories/PeopleRepository.cs | 13 +- .../Repositories/ProviderRepository.cs | 13 +- .../Repositories/SeasonRepository.cs | 13 +- .../Repositories/ShowRepository.cs | 25 +--- .../Repositories/StudioRepository.cs | 13 +- .../Repositories/TrackRepository.cs | 13 +- Kyoo/Models/DatabaseContext.cs | 118 ++++++++++++++++-- ....cs => 20200803005331_Initial.Designer.cs} | 15 ++- ...7_Initial.cs => 20200803005331_Initial.cs} | 25 ++-- .../Internal/DatabaseContextModelSnapshot.cs | 13 +- Kyoo/Tasks/MetadataLoader.cs | 8 +- Kyoo/Views/API/SeasonApi.cs | 24 ++-- Kyoo/Views/WebClient | 2 +- 19 files changed, 166 insertions(+), 195 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20200724211017_Initial.Designer.cs => 20200803005331_Initial.Designer.cs} (98%) rename Kyoo/Models/DatabaseMigrations/Internal/{20200724211017_Initial.cs => 20200803005331_Initial.cs} (98%) diff --git a/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs b/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs index c8836c0d..6b04c8b2 100644 --- a/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs +++ b/Kyoo.Common/Models/Exceptions/DuplicatedItemException.cs @@ -6,6 +6,8 @@ namespace Kyoo.Models.Exceptions { public override string Message { get; } + public DuplicatedItemException() {} + public DuplicatedItemException(string message) { Message = message; diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index cfc1325a..2bb43812 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -169,11 +169,5 @@ namespace Kyoo.Controllers 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/Controllers/Repositories/CollectionRepository.cs b/Kyoo/Controllers/Repositories/CollectionRepository.cs index 84ddc900..533b4a6f 100644 --- a/Kyoo/Controllers/Repositories/CollectionRepository.cs +++ b/Kyoo/Controllers/Repositories/CollectionRepository.cs @@ -56,19 +56,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Added; - - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index 0392564c..bf8fa6a9 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -84,20 +84,8 @@ namespace Kyoo.Controllers if (obj.Tracks != null) foreach (Track entry in obj.Tracks) _database.Entry(entry).State = EntityState.Added; - - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); - throw; - } - + + await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/GenreRepository.cs b/Kyoo/Controllers/Repositories/GenreRepository.cs index 9f068e68..f3a93873 100644 --- a/Kyoo/Controllers/Repositories/GenreRepository.cs +++ b/Kyoo/Controllers/Repositories/GenreRepository.cs @@ -51,20 +51,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Added; - - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated genre (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated genre (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 5df3c071..da1dfd84 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -61,18 +61,7 @@ namespace Kyoo.Controllers foreach (ProviderLink entry in obj.ProviderLinks) _database.Entry(entry).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index b7b97572..67fdf775 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -61,18 +61,7 @@ namespace Kyoo.Controllers foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/ProviderRepository.cs b/Kyoo/Controllers/Repositories/ProviderRepository.cs index a74dc9b5..79025b00 100644 --- a/Kyoo/Controllers/Repositories/ProviderRepository.cs +++ b/Kyoo/Controllers/Repositories/ProviderRepository.cs @@ -35,18 +35,7 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index 5668a25d..d48c340e 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -89,18 +89,7 @@ namespace Kyoo.Controllers foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 2c0e1a16..1f02fb50 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -102,18 +102,7 @@ namespace Kyoo.Controllers foreach (MetadataID entry in obj.ExternalIDs) _database.Entry(entry).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); - throw; - } - + await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); return obj; } @@ -139,23 +128,17 @@ namespace Kyoo.Controllers { if (collectionID != null) { - _database.CollectionLinks.AddIfNotExist(new CollectionLink { CollectionID = collectionID, ShowID = showID}, - x => x.CollectionID == collectionID && x.ShowID == showID); + await _database.CollectionLinks.AddAsync(new CollectionLink {CollectionID = collectionID, ShowID = showID}); } if (libraryID != null) { - _database.LibraryLinks.AddIfNotExist(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}, - x => x.LibraryID == libraryID.Value && x.CollectionID == null && x.ShowID == showID); + await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); } if (libraryID != null && collectionID != null) { - _database.LibraryLinks.AddIfNotExist( - new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}, - x => x.LibraryID == libraryID && x.CollectionID == collectionID && x.ShowID == null); + await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); } - - await _database.SaveChangesAsync(); } public override async Task Delete(Show obj) diff --git a/Kyoo/Controllers/Repositories/StudioRepository.cs b/Kyoo/Controllers/Repositories/StudioRepository.cs index 65ec054e..e2680341 100644 --- a/Kyoo/Controllers/Repositories/StudioRepository.cs +++ b/Kyoo/Controllers/Repositories/StudioRepository.cs @@ -34,18 +34,7 @@ namespace Kyoo.Controllers throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Added; - - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); - throw; - } + await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 01334f2f..805d5736 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -4,7 +4,6 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; -using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; namespace Kyoo.Controllers @@ -66,17 +65,7 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Added; - try - { - await _database.SaveChangesAsync(); - } - catch (DbUpdateException ex) - { - _database.DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); - throw; - } + await _database.SaveChangesAsync($"Trying to insert a duplicated track (slug {obj.Slug} already exists)."); return obj; } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index ac54a80c..3de20d02 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using IdentityServer4.EntityFramework.Entities; using IdentityServer4.EntityFramework.Extensions; using IdentityServer4.EntityFramework.Interfaces; using IdentityServer4.EntityFramework.Options; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.Extensions.Options; +using Npgsql; namespace Kyoo { @@ -175,8 +178,112 @@ namespace Kyoo modelBuilder.Entity() .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) .IsUnique(); + modelBuilder.Entity() + .HasIndex(x => new {x.LibraryID, x.ShowID, x.CollectionID}) + .IsUnique(); + modelBuilder.Entity() + .HasIndex(x => new {x.CollectionID, x.ShowID}) + .IsUnique(); } - + + public override int SaveChanges() + { + try + { + return base.SaveChanges(); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(); + throw; + } + } + + public override int SaveChanges(bool acceptAllChangesOnSuccess) + { + try + { + return base.SaveChanges(acceptAllChangesOnSuccess); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(); + throw; + } + } + + public int SaveChanges(string duplicateMessage) + { + try + { + return base.SaveChanges(); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(duplicateMessage); + throw; + } + } + + public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess, + CancellationToken cancellationToken = new CancellationToken()) + { + try + { + return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(); + throw; + } + } + + public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + try + { + return await base.SaveChangesAsync(cancellationToken); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(); + throw; + } + } + + public async Task SaveChangesAsync(string duplicateMessage, + CancellationToken cancellationToken = new CancellationToken()) + { + try + { + return await base.SaveChangesAsync(cancellationToken); + } + catch (DbUpdateException ex) + { + DiscardChanges(); + if (IsDuplicateException(ex)) + throw new DuplicatedItemException(duplicateMessage); + throw; + } + } + + public static bool IsDuplicateException(DbUpdateException ex) + { + return ex.InnerException is PostgresException inner + && inner.SqlState == PostgresErrorCodes.UniqueViolation; + } + public void DiscardChanges() { foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged @@ -186,13 +293,4 @@ namespace Kyoo } } } -} - -public static class DbSetExtension -{ - public static EntityEntry AddIfNotExist(this DbSet db, T entity, Func predicate) where T : class - { - bool exists = db.Any(predicate); - return exists ? null : db.Add(entity); - } } \ No newline at end of file diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs similarity index 98% rename from Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs index 36e43ea8..7cea0f70 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20200724211017_Initial")] + [Migration("20200803005331_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -28,9 +28,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ImgPrimary") - .HasColumnType("text"); - b.Property("Name") .HasColumnType("text"); @@ -66,10 +63,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasKey("ID"); - b.HasIndex("CollectionID"); - b.HasIndex("ShowID"); + b.HasIndex("CollectionID", "ShowID") + .IsUnique(); + b.ToTable("CollectionLinks"); }); @@ -203,10 +201,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("CollectionID"); - b.HasIndex("LibraryID"); - b.HasIndex("ShowID"); + b.HasIndex("LibraryID", "ShowID", "CollectionID") + .IsUnique(); + b.ToTable("LibraryLinks"); }); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs similarity index 98% rename from Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs index 93aadbae..77171a55 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200724211017_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs @@ -17,8 +17,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal Slug = table.Column(nullable: true), Name = table.Column(nullable: true), Poster = table.Column(nullable: true), - Overview = table.Column(nullable: true), - ImgPrimary = table.Column(nullable: true) + Overview = table.Column(nullable: true) }, constraints: table => { @@ -402,16 +401,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateIndex( - name: "IX_CollectionLinks_CollectionID", - table: "CollectionLinks", - column: "CollectionID"); - migrationBuilder.CreateIndex( name: "IX_CollectionLinks_ShowID", table: "CollectionLinks", column: "ShowID"); + migrationBuilder.CreateIndex( + name: "IX_CollectionLinks_CollectionID_ShowID", + table: "CollectionLinks", + columns: new[] { "CollectionID", "ShowID" }, + unique: true); + migrationBuilder.CreateIndex( name: "IX_Collections_Slug", table: "Collections", @@ -451,16 +451,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal table: "LibraryLinks", column: "CollectionID"); - migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_LibraryID", - table: "LibraryLinks", - column: "LibraryID"); - migrationBuilder.CreateIndex( name: "IX_LibraryLinks_ShowID", table: "LibraryLinks", column: "ShowID"); + migrationBuilder.CreateIndex( + name: "IX_LibraryLinks_LibraryID_ShowID_CollectionID", + table: "LibraryLinks", + columns: new[] { "LibraryID", "ShowID", "CollectionID" }, + unique: true); + migrationBuilder.CreateIndex( name: "IX_MetadataIds_EpisodeID", table: "MetadataIds", diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 7e9761b4..1515ac3f 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -26,9 +26,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ImgPrimary") - .HasColumnType("text"); - b.Property("Name") .HasColumnType("text"); @@ -64,10 +61,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasKey("ID"); - b.HasIndex("CollectionID"); - b.HasIndex("ShowID"); + b.HasIndex("CollectionID", "ShowID") + .IsUnique(); + b.ToTable("CollectionLinks"); }); @@ -201,10 +199,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("CollectionID"); - b.HasIndex("LibraryID"); - b.HasIndex("ShowID"); + b.HasIndex("LibraryID", "ShowID", "CollectionID") + .IsUnique(); + b.ToTable("LibraryLinks"); }); diff --git a/Kyoo/Tasks/MetadataLoader.cs b/Kyoo/Tasks/MetadataLoader.cs index 3d272217..1fb923d9 100644 --- a/Kyoo/Tasks/MetadataLoader.cs +++ b/Kyoo/Tasks/MetadataLoader.cs @@ -17,15 +17,13 @@ namespace Kyoo.Tasks public bool RunOnStartup => true; public int Priority => 1000; - public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) + public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null) { using IServiceScope serviceScope = serviceProvider.CreateScope(); - DatabaseContext database = serviceScope.ServiceProvider.GetService(); + IProviderRepository providers = serviceScope.ServiceProvider.GetService(); IPluginManager pluginManager = serviceScope.ServiceProvider.GetService(); foreach (IMetadataProvider provider in pluginManager.GetPlugins()) - database.Providers.AddIfNotExist(provider.Provider, x => x.Slug == provider.Provider.Slug); - database.SaveChanges(); - return Task.CompletedTask; + await providers.CreateIfNotExists(provider.Provider); } public Task> GetPossibleParameters() diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index c46f5ce5..c7da45d9 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -24,14 +24,14 @@ namespace Kyoo.Api _libraryManager = libraryManager; } - [HttpGet("{seasonID:int}/season")] - [HttpGet("{seasonID:int}/seasons")] + [HttpGet("{seasonID:int}/episode")] + [HttpGet("{seasonID:int}/episodes")] [Authorize(Policy = "Read")] - public async Task>> GetSeasons(int seasonID, + public async Task>> GetEpisode(int seasonID, [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) + [FromQuery] int limit = 30) { where.Remove("sortBy"); where.Remove("limit"); @@ -56,15 +56,15 @@ namespace Kyoo.Api } } - [HttpGet("{showSlug}-{seasonNumber:int}/season")] - [HttpGet("{showSlug}-{seasonNumber:int}/seasons")] + [HttpGet("{showSlug}-{seasonNumber:int}/episode")] + [HttpGet("{showSlug}-{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] - public async Task>> GetSeasons(string showSlug, + public async Task>> GetEpisode(string showSlug, int seasonNumber, [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) + [FromQuery] int limit = 30) { where.Remove("sortBy"); where.Remove("limit"); @@ -90,15 +90,15 @@ namespace Kyoo.Api } } - [HttpGet("{showID:int}-{seasonNumber:int}/season")] - [HttpGet("{showID:int}-{seasonNumber:int}/seasons")] + [HttpGet("{showID:int}-{seasonNumber:int}/episode")] + [HttpGet("{showID:int}-{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] - public async Task>> GetSeasons(int showID, + public async Task>> GetEpisode(int showID, int seasonNumber, [FromQuery] string sortBy, [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) + [FromQuery] int limit = 30) { where.Remove("sortBy"); where.Remove("limit"); diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index d692f9f3..db058928 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit d692f9f3d364d7d2748205374a3a84fb1fd4a938 +Subproject commit db0589285c0790c0f2a2f5beb98fb37303256ec3 From 8af2cff1a2a8368e539d6cbe1b82384e840a081f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 3 Aug 2020 06:01:38 +0200 Subject: [PATCH 31/36] Fixing library links duplicates --- Kyoo/Controllers/Repositories/ShowRepository.cs | 3 +++ Kyoo/Models/DatabaseContext.cs | 17 ++++++++++++++++- ...er.cs => 20200803040029_Initial.Designer.cs} | 7 +++++-- ...331_Initial.cs => 20200803040029_Initial.cs} | 10 ++++++++-- .../Internal/DatabaseContextModelSnapshot.cs | 5 ++++- 5 files changed, 36 insertions(+), 6 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20200803005331_Initial.Designer.cs => 20200803040029_Initial.Designer.cs} (99%) rename Kyoo/Models/DatabaseMigrations/Internal/{20200803005331_Initial.cs => 20200803040029_Initial.cs} (98%) diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 1f02fb50..77b18b56 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -129,15 +129,18 @@ namespace Kyoo.Controllers if (collectionID != null) { await _database.CollectionLinks.AddAsync(new CollectionLink {CollectionID = collectionID, ShowID = showID}); + await _database.SaveIfNoDuplicates(); } if (libraryID != null) { await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, ShowID = showID}); + await _database.SaveIfNoDuplicates(); } if (libraryID != null && collectionID != null) { await _database.LibraryLinks.AddAsync(new LibraryLink {LibraryID = libraryID.Value, CollectionID = collectionID.Value}); + await _database.SaveIfNoDuplicates(); } } diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 3de20d02..f6157af7 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -179,7 +179,10 @@ namespace Kyoo .HasIndex(x => new {x.ShowID, x.SeasonNumber, x.EpisodeNumber, x.AbsoluteNumber}) .IsUnique(); modelBuilder.Entity() - .HasIndex(x => new {x.LibraryID, x.ShowID, x.CollectionID}) + .HasIndex(x => new {x.LibraryID, x.ShowID}) + .IsUnique(); + modelBuilder.Entity() + .HasIndex(x => new {x.LibraryID, x.CollectionID}) .IsUnique(); modelBuilder.Entity() .HasIndex(x => new {x.CollectionID, x.ShowID}) @@ -278,6 +281,18 @@ namespace Kyoo } } + public async Task SaveIfNoDuplicates(CancellationToken cancellationToken = new CancellationToken()) + { + try + { + return await SaveChangesAsync(cancellationToken); + } + catch (DuplicatedItemException) + { + return -1; + } + } + public static bool IsDuplicateException(DbUpdateException ex) { return ex.InnerException is PostgresException inner diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs similarity index 99% rename from Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs index 7cea0f70..825758b2 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs @@ -10,7 +10,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20200803005331_Initial")] + [Migration("20200803040029_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -203,7 +203,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("ShowID"); - b.HasIndex("LibraryID", "ShowID", "CollectionID") + b.HasIndex("LibraryID", "CollectionID") + .IsUnique(); + + b.HasIndex("LibraryID", "ShowID") .IsUnique(); b.ToTable("LibraryLinks"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs similarity index 98% rename from Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs index 77171a55..c42433a5 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200803005331_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs @@ -457,9 +457,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal column: "ShowID"); migrationBuilder.CreateIndex( - name: "IX_LibraryLinks_LibraryID_ShowID_CollectionID", + name: "IX_LibraryLinks_LibraryID_CollectionID", table: "LibraryLinks", - columns: new[] { "LibraryID", "ShowID", "CollectionID" }, + columns: new[] { "LibraryID", "CollectionID" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LibraryLinks_LibraryID_ShowID", + table: "LibraryLinks", + columns: new[] { "LibraryID", "ShowID" }, unique: true); migrationBuilder.CreateIndex( diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 1515ac3f..762ea73d 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -201,7 +201,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("ShowID"); - b.HasIndex("LibraryID", "ShowID", "CollectionID") + b.HasIndex("LibraryID", "CollectionID") + .IsUnique(); + + b.HasIndex("LibraryID", "ShowID") .IsUnique(); b.ToTable("LibraryLinks"); From 6a0fb2dd386caf62d0f1bc642f5de3d64b9bf835 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 3 Aug 2020 17:29:17 +0200 Subject: [PATCH 32/36] Creating the collection's rest api --- Kyoo.Common/Controllers/ILibraryManager.cs | 42 ++++- Kyoo.Common/Controllers/IRepository.cs | 58 ++++--- .../Implementations/LibraryManager.cs | 32 ++++ .../Repositories/LibraryRepository.cs | 32 ++++ .../Repositories/ShowRepository.cs | 32 ++++ Kyoo/Views/API/CollectionAPI.cs | 20 --- Kyoo/Views/API/CollectionApi.cs | 155 ++++++++++++++++++ Kyoo/Views/WebClient | 2 +- 8 files changed, 331 insertions(+), 42 deletions(-) delete mode 100644 Kyoo/Views/API/CollectionAPI.cs create mode 100644 Kyoo/Views/API/CollectionApi.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 52acb660..3da6fd8f 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -237,7 +237,6 @@ namespace Kyoo.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetItemsFromLibrary(int id, [Optional] Expression> where, Expression> sort, @@ -248,12 +247,51 @@ namespace Kyoo.Controllers Expression> where = null, Sort sort = default, Pagination limit = default); - Task> GetItemsFromLibrary(string librarySlug, [Optional] Expression> where, Expression> sort, Pagination limit = default ) => GetItemsFromLibrary(librarySlug, where, new Sort(sort), limit); + + Task> GetShowsFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetShowsFromCollection(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetShowsFromCollection(id, where, new Sort(sort), limit); + + Task> GetShowsFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetShowsFromCollection(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetShowsFromCollection(slug, where, new Sort(sort), limit); + + Task> GetLibrariesFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetLibrariesFromCollection(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetLibrariesFromCollection(id, where, new Sort(sort), limit); + + Task> GetLibrariesFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetLibrariesFromCollection(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetLibrariesFromCollection(slug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index e2e5a2c0..1553951e 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -123,25 +123,25 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetFromLibrary(slug, where, new Sort(sort), limit); - // Task> GetFromCollection(int id, - // Expression> where = null, - // Sort sort = default, - // Pagination limit = default); - // Task> GetFromCollection(int id, - // [Optional] Expression> where, - // Expression> sort, - // Pagination limit = default - // ) => GetFromCollection(id, where, new Sort(sort), limit); - // - // Task> GetFromCollection(string slug, - // Expression> where = null, - // Sort sort = default, - // Pagination limit = default); - // Task> GetFromCollection(string slug, - // [Optional] Expression> where, - // Expression> sort, - // Pagination limit = default - // ) => GetFromCollection(slug, where, new Sort(sort), limit); + Task> GetFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromCollection(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromCollection(id, where, new Sort(sort), limit); + + Task> GetFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromCollection(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromCollection(slug, where, new Sort(sort), limit); } public interface ISeasonRepository : IRepository @@ -255,6 +255,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromCollection(int id, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromCollection(id, where, new Sort(sort), limit); + + Task> GetFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromCollection(string slug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromCollection(slug, where, new Sort(sort), limit); } public interface ILibraryItemRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index fd6c2e79..82f0a8b3 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -380,6 +380,38 @@ namespace Kyoo.Controllers return LibraryItemRepository.GetFromLibrary(librarySlug, where, sort, limit); } + public Task> GetShowsFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ShowRepository.GetFromCollection(id, where, sort, limit); + } + + public Task> GetShowsFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return ShowRepository.GetFromCollection(slug, where, sort, limit); + } + + public Task> GetLibrariesFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryRepository.GetFromCollection(id, where, sort, limit); + } + + public Task> GetLibrariesFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return LibraryRepository.GetFromCollection(slug, where, sort, limit); + } + public Task AddShowLink(int showID, int? libraryID, int? collectionID) { return ShowRepository.AddShowLink(showID, libraryID, collectionID); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index da1dfd84..db39e22d 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -125,5 +125,37 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return libraries; } + + public async Task> GetFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection libraries = await ApplyFilters(_database.LibraryLinks + .Where(x => x.CollectionID == id) + .Select(x => x.Library), + where, + sort, + limit); + if (!libraries.Any() && await _shows.Value.Get(id) == null) + throw new ItemNotFound(); + return libraries; + } + + public async Task> GetFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection libraries = await ApplyFilters(_database.LibraryLinks + .Where(x => x.Collection.Slug == slug) + .Select(x => x.Library), + where, + sort, + limit); + if (!libraries.Any() && await _shows.Value.Get(slug) == null) + throw new ItemNotFound(); + return libraries; + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 77b18b56..36e285a6 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -211,5 +211,37 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return shows; } + + public async Task> GetFromCollection(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection shows = await ApplyFilters(_database.CollectionLinks + .Where(x => x.CollectionID== id) + .Select(x => x.Show), + where, + sort, + limit); + if (!shows.Any() && await _libraries.Value.Get(id) == null) + throw new ItemNotFound(); + return shows; + } + + public async Task> GetFromCollection(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection shows = await ApplyFilters(_database.CollectionLinks + .Where(x => x.Collection.Slug == slug) + .Select(x => x.Show), + where, + sort, + limit); + if (!shows.Any() && await _libraries.Value.Get(slug) == null) + throw new ItemNotFound(); + return shows; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/CollectionAPI.cs b/Kyoo/Views/API/CollectionAPI.cs deleted file mode 100644 index c4223e20..00000000 --- a/Kyoo/Views/API/CollectionAPI.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Kyoo.Controllers; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; -using Kyoo.CommonApi; -using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Configuration; - -namespace Kyoo.Api -{ - [Route("api/collection")] - [Route("api/collections")] - [ApiController] - public class CollectionApi : CrudApi - { - public CollectionApi(ICollectionRepository repository, IConfiguration configuration) - : base(repository, configuration) - { } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/CollectionApi.cs b/Kyoo/Views/API/CollectionApi.cs new file mode 100644 index 00000000..bb9675d2 --- /dev/null +++ b/Kyoo/Views/API/CollectionApi.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/collection")] + [Route("api/collections")] + [ApiController] + public class CollectionApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public CollectionApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.CollectionRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{id:int}/show")] + [HttpGet("{id:int}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShowsFromCollection(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/show")] + [HttpGet("{slug}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShowsFromCollection(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{id:int}/library")] + [HttpGet("{id:int}/libraries")] + [Authorize(Policy = "Read")] + public async Task>> GetLibraries(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetLibrariesFromCollection(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/library")] + [HttpGet("{slug}/libraries")] + [Authorize(Policy = "Read")] + public async Task>> GetLibraries(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetLibrariesFromCollection(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index db058928..de1b3b06 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit db0589285c0790c0f2a2f5beb98fb37303256ec3 +Subproject commit de1b3b069aa4f920bd5248223eda151b1eedf541 From 2df3562045c4c5e4ccadc143badfed87ac13b3cd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 4 Aug 2020 20:18:33 +0200 Subject: [PATCH 33/36] Updating the search api --- Kyoo.Common/Controllers/ILibraryManager.cs | 20 ++++ Kyoo.Common/Controllers/IRepository.cs | 20 ++++ .../Implementations/LibraryManager.cs | 16 ++++ Kyoo.Common/Models/PeopleLink.cs | 80 +++++++++++++++- Kyoo.Common/Models/Resources/Collection.cs | 2 +- Kyoo.Common/Models/Resources/People.cs | 6 +- Kyoo.CommonAPI/JsonSerializer.cs | 5 +- .../Repositories/PeopleRepository.cs | 42 ++++++++- .../Repositories/ShowRepository.cs | 5 +- Kyoo/Controllers/ThumbnailsManager.cs | 4 +- Kyoo/Models/DatabaseContext.cs | 47 ++++++---- ....cs => 20200804172021_Initial.Designer.cs} | 20 ++-- ...9_Initial.cs => 20200804172021_Initial.cs} | 29 +++--- .../Internal/DatabaseContextModelSnapshot.cs | 18 ++-- Kyoo/Views/API/PeopleAPI.cs | 36 ------- Kyoo/Views/API/PeopleApi.cs | 93 +++++++++++++++++++ Kyoo/Views/API/{SearchAPI.cs => SearchApi.cs} | 9 +- Kyoo/Views/WebClient | 2 +- 18 files changed, 351 insertions(+), 103 deletions(-) rename Kyoo/Models/DatabaseMigrations/Internal/{20200803040029_Initial.Designer.cs => 20200804172021_Initial.Designer.cs} (97%) rename Kyoo/Models/DatabaseMigrations/Internal/{20200803040029_Initial.cs => 20200804172021_Initial.cs} (96%) delete mode 100644 Kyoo/Views/API/PeopleAPI.cs create mode 100644 Kyoo/Views/API/PeopleApi.cs rename Kyoo/Views/API/{SearchAPI.cs => SearchApi.cs} (80%) diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 3da6fd8f..e79bb825 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -292,6 +292,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetLibrariesFromCollection(slug, where, new Sort(sort), limit); + + Task> GetRolesFromPeople(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetRolesFromPeople(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetRolesFromPeople(showID, where, new Sort(sort), limit); + + Task> GetRolesFromPeople(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetRolesFromPeople(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetRolesFromPeople(showSlug, where, new Sort(sort), limit); // Helpers diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 1553951e..2c4f5c23 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -395,6 +395,26 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); + + Task> GetFromPeople(int showID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromPeople(int showID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromPeople(showID, where, new Sort(sort), limit); + + Task> GetFromPeople(string showSlug, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromPeople(string showSlug, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromPeople(showSlug, where, new Sort(sort), limit); } public interface IProviderRepository : IRepository {} } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 82f0a8b3..db85d358 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -411,6 +411,22 @@ namespace Kyoo.Controllers { return LibraryRepository.GetFromCollection(slug, where, sort, limit); } + + public Task> GetRolesFromPeople(int id, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return PeopleRepository.GetFromPeople(id, where, sort, limit); + } + + public Task> GetRolesFromPeople(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return PeopleRepository.GetFromPeople(slug, where, sort, limit); + } public Task AddShowLink(int showID, int? libraryID, int? collectionID) { diff --git a/Kyoo.Common/Models/PeopleLink.cs b/Kyoo.Common/Models/PeopleLink.cs index f095d802..04555298 100644 --- a/Kyoo.Common/Models/PeopleLink.cs +++ b/Kyoo.Common/Models/PeopleLink.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq.Expressions; using Newtonsoft.Json; namespace Kyoo.Models @@ -21,6 +23,12 @@ namespace Kyoo.Models set => People.Name = value; } + public string Poster + { + get => People.Poster; + set => People.Poster = value; + } + public IEnumerable ExternalIDs { get => People.ExternalIDs; @@ -42,11 +50,79 @@ namespace Kyoo.Models Type = type; } - public PeopleLink(string slug, string name, string role, string type, string imgPrimary, IEnumerable externalIDs) + public PeopleLink(string slug, + string name, + string role, + string type, + string poster, + IEnumerable externalIDs) { - People = new People(slug, name, imgPrimary, externalIDs); + People = new People(slug, name, poster, externalIDs); Role = role; Type = type; } } + + public class ShowRole : IResource + { + public int ID { get; set; } + public string Role { get; set; } + public string Type { get; set; } + + public string Slug { get; set; } + public string Title { get; set; } + public IEnumerable Aliases { get; set; } + [JsonIgnore] public string Path { get; set; } + public string Overview { get; set; } + public Status? Status { get; set; } + public string TrailerUrl { get; set; } + public int? StartYear { get; set; } + public int? EndYear { get; set; } + public string Poster { get; set; } + public string Logo { get; set; } + public string Backdrop { get; set; } + public bool IsMovie { get; set; } + + public ShowRole() {} + + public ShowRole(PeopleLink x) + { + ID = x.ID; + Role = x.Role; + Type = x.Type; + Slug = x.Show.Slug; + Title = x.Show.Title; + Aliases = x.Show.Aliases; + Path = x.Show.Path; + Overview = x.Show.Overview; + Status = x.Show.Status; + TrailerUrl = x.Show.TrailerUrl; + StartYear = x.Show.StartYear; + EndYear = x.Show.EndYear; + Poster = x.Show.Poster; + Logo = x.Show.Logo; + Backdrop = x.Show.Backdrop; + IsMovie = x.Show.IsMovie; + } + + public static Expression> FromPeopleRole => x => new ShowRole + { + ID = x.ID, + Role = x.Role, + Type = x.Type, + Slug = x.Show.Slug, + Title = x.Show.Title, + Aliases = x.Show.Aliases, + Path = x.Show.Path, + Overview = x.Show.Overview, + Status = x.Show.Status, + TrailerUrl = x.Show.TrailerUrl, + StartYear = x.Show.StartYear, + EndYear = x.Show.EndYear, + Poster = x.Show.Poster, + Logo = x.Show.Logo, + Backdrop = x.Show.Backdrop, + IsMovie = x.Show.IsMovie + }; + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index eb932de2..07bae5bc 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -13,7 +13,7 @@ namespace Kyoo.Models public string Poster { get; set; } public string Overview { get; set; } [NotMergable] [JsonIgnore] public virtual IEnumerable Links { get; set; } - public virtual IEnumerable Shows + [JsonIgnore] public virtual IEnumerable Shows { get => Links.Select(x => x.Show); set => Links = value.Select(x => new CollectionLink(this, x)); diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index b2bc658e..78a9c87d 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -9,18 +9,18 @@ namespace Kyoo.Models public int ID { get; set; } public string Slug { get; set; } public string Name { get; set; } - [JsonIgnore] public string ImgPrimary { get; set; } + public string Poster { get; set; } public virtual IEnumerable ExternalIDs { get; set; } [JsonIgnore] public virtual IEnumerable Roles { get; set; } public People() {} - public People(string slug, string name, string imgPrimary, IEnumerable externalIDs) + public People(string slug, string name, string poster, IEnumerable externalIDs) { Slug = slug; Name = name; - ImgPrimary = imgPrimary; + Poster = poster; ExternalIDs = externalIDs; } } diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 42c035f6..11c696fe 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -77,8 +77,9 @@ namespace Kyoo.Controllers { ContractResolver = new JsonPropertySelector(null, new Dictionary>() { - {typeof(Show), new HashSet {"genres", "studio", "people", "seasons"}}, - {typeof(Episode), new HashSet {"tracks"}} + {typeof(Show), new HashSet {"genres", "studio"}}, + {typeof(Episode), new HashSet {"tracks"}}, + {typeof(PeopleLink), new HashSet {"show"}} }) }, context.HttpContext.RequestServices.GetRequiredService>(), diff --git a/Kyoo/Controllers/Repositories/PeopleRepository.cs b/Kyoo/Controllers/Repositories/PeopleRepository.cs index 67fdf775..a9f2e920 100644 --- a/Kyoo/Controllers/Repositories/PeopleRepository.cs +++ b/Kyoo/Controllers/Repositories/PeopleRepository.cs @@ -102,8 +102,8 @@ namespace Kyoo.Controllers }; } - ICollection people = await ApplyFilters(_database.PeopleLinks.Where(x => x.ShowID == showID), - id => _database.PeopleLinks.FirstOrDefaultAsync(x => x.ID == id), + ICollection people = await ApplyFilters(_database.PeopleRoles.Where(x => x.ShowID == showID), + id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.People.Name, where, sort, @@ -128,8 +128,8 @@ namespace Kyoo.Controllers }; } - ICollection people = await ApplyFilters(_database.PeopleLinks.Where(x => x.Show.Slug == showSlug), - id => _database.PeopleLinks.FirstOrDefaultAsync(x => x.ID == id), + ICollection people = await ApplyFilters(_database.PeopleRoles.Where(x => x.Show.Slug == showSlug), + id => _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id), x => x.People.Name, where, sort, @@ -138,5 +138,39 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return people; } + + public async Task> GetFromPeople(int peopleID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.PeopleID == peopleID) + .Select(ShowRole.FromPeopleRole), + async id => new ShowRole(await _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id)), + x => x.Title, + where, + sort, + limit); + if (!roles.Any() && await Get(peopleID) == null) + throw new ItemNotFound(); + return roles; + } + + public async Task> GetFromPeople(string slug, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection roles = await ApplyFilters(_database.PeopleRoles.Where(x => x.People.Slug == slug) + .Select(ShowRole.FromPeopleRole), + async id => new ShowRole(await _database.PeopleRoles.FirstOrDefaultAsync(x => x.ID == id)), + x => x.Title, + where, + sort, + limit); + if (!roles.Any() && await Get(slug) == null) + throw new ItemNotFound(); + return roles; + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 36e285a6..9ea7906c 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -78,9 +78,10 @@ namespace Kyoo.Controllers public override async Task> Search(string query) { + query = $"%{query}%"; return await _database.Shows - .Where(x => EF.Functions.ILike(x.Title, $"%{query}%") - /*|| EF.Functions.ILike(x.Aliases, $"%{query}%")*/) + .Where(x => EF.Functions.ILike(x.Title, query) + /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 75f8ee5c..ba40992a 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -68,10 +68,10 @@ namespace Kyoo.Controllers foreach (PeopleLink peop in people) { string localPath = Path.Combine(root, peop.People.Slug + ".jpg"); - if (peop.People.ImgPrimary == null) + if (peop.People.Poster == null) continue; if (alwaysDownload || !File.Exists(localPath)) - await DownloadImage(peop.People.ImgPrimary, localPath, $"The profile picture of {peop.People.Name}"); + await DownloadImage(peop.People.Poster, localPath, $"The profile picture of {peop.People.Name}"); } return people; diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index f6157af7..e730ca69 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -9,6 +9,7 @@ using IdentityServer4.EntityFramework.Interfaces; using IdentityServer4.EntityFramework.Options; using Kyoo.Models; using Kyoo.Models.Exceptions; +using Kyoo.Models.Watch; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; @@ -68,37 +69,46 @@ namespace Kyoo public DbSet Providers { get; set; } public DbSet MetadataIds { get; set; } - public DbSet LibraryLinks { get; set; } - public DbSet CollectionLinks { get; set; } - public DbSet PeopleLinks { get; set; } + public DbSet PeopleRoles { get; set; } + // This is used because EF doesn't support Many-To-Many relationships so for now we need to override the getter/setters to store this. + public DbSet LibraryLinks { get; set; } + public DbSet CollectionLinks { get; set; } public DbSet GenreLinks { get; set; } public DbSet ProviderLinks { get; set; } - - - private readonly ValueConverter, string> _stringArrayConverter = - new ValueConverter, string>( - arr => string.Join("|", arr), - str => str.Split("|", StringSplitOptions.None)); + public DatabaseContext() + { + NpgsqlConnection.GlobalTypeMapper.MapEnum(); + NpgsqlConnection.GlobalTypeMapper.MapEnum(); + NpgsqlConnection.GlobalTypeMapper.MapEnum(); + } + private readonly ValueComparer> _stringArrayComparer = new ValueComparer>( - (l1, l2) => l1.SequenceEqual(l2), - arr => arr.Aggregate(0, (i, s) => s.GetHashCode())); - + (l1, l2) => l1.SequenceEqual(l2), + arr => arr.Aggregate(0, (i, s) => s.GetHashCode())); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().Property(e => e.Paths) - .HasConversion(_stringArrayConverter).Metadata - .SetValueComparer(_stringArrayComparer); - modelBuilder.Entity().Property(e => e.Aliases) - .HasConversion(_stringArrayConverter).Metadata - .SetValueComparer(_stringArrayComparer); + modelBuilder.HasPostgresEnum(); + modelBuilder.HasPostgresEnum(); + modelBuilder.HasPostgresEnum(); + modelBuilder.Entity() + .Property(x => x.Paths) + .HasColumnType("text[]") + .Metadata.SetValueComparer(_stringArrayComparer); + + modelBuilder.Entity() + .Property(x => x.Aliases) + .HasColumnType("text[]") + .Metadata.SetValueComparer(_stringArrayComparer); + + modelBuilder.Entity() .Property(t => t.IsDefault) .ValueGeneratedNever(); @@ -127,6 +137,7 @@ namespace Kyoo modelBuilder.Entity() .Ignore(x => x.Slug) .Ignore(x => x.Name) + .Ignore(x => x.Poster) .Ignore(x => x.ExternalIDs); modelBuilder.Entity() diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.Designer.cs similarity index 97% rename from Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.Designer.cs index 825758b2..0e398aef 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.Designer.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.Designer.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Kyoo; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -10,13 +11,16 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Kyoo.Models.DatabaseMigrations.Internal { [DbContext(typeof(DatabaseContext))] - [Migration("20200803040029_Initial")] + [Migration("20200804172021_Initial")] partial class Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder + .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") + .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") + .HasAnnotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("ProductVersion", "3.1.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); @@ -167,8 +171,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); - b.Property("Paths") - .HasColumnType("text"); + b.Property>("Paths") + .HasColumnType("text[]"); b.Property("Slug") .HasColumnType("text"); @@ -262,10 +266,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ImgPrimary") + b.Property("Name") .HasColumnType("text"); - b.Property("Name") + b.Property("Poster") .HasColumnType("text"); b.Property("Slug") @@ -304,7 +308,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("ShowID"); - b.ToTable("PeopleLinks"); + b.ToTable("PeopleRoles"); }); modelBuilder.Entity("Kyoo.Models.ProviderID", b => @@ -393,8 +397,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Aliases") - .HasColumnType("text"); + b.Property>("Aliases") + .HasColumnType("text[]"); b.Property("Backdrop") .HasColumnType("text"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs b/Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.cs similarity index 96% rename from Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs rename to Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.cs index c42433a5..d94bb616 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/20200803040029_Initial.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/20200804172021_Initial.cs @@ -8,6 +8,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:item_type", "show,movie,collection") + .Annotation("Npgsql:Enum:status", "finished,airing,planned,unknown") + .Annotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle"); + migrationBuilder.CreateTable( name: "Collections", columns: table => new @@ -46,7 +51,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Slug = table.Column(nullable: true), Name = table.Column(nullable: true), - Paths = table.Column(nullable: true) + Paths = table.Column(type: "text[]", nullable: true) }, constraints: table => { @@ -61,7 +66,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Slug = table.Column(nullable: true), Name = table.Column(nullable: true), - ImgPrimary = table.Column(nullable: true) + Poster = table.Column(nullable: true) }, constraints: table => { @@ -131,7 +136,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), Slug = table.Column(nullable: true), Title = table.Column(nullable: true), - Aliases = table.Column(nullable: true), + Aliases = table.Column(type: "text[]", nullable: true), Path = table.Column(nullable: true), Overview = table.Column(nullable: true), Status = table.Column(nullable: true), @@ -239,7 +244,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }); migrationBuilder.CreateTable( - name: "PeopleLinks", + name: "PeopleRoles", columns: table => new { ID = table.Column(nullable: false) @@ -251,15 +256,15 @@ namespace Kyoo.Models.DatabaseMigrations.Internal }, constraints: table => { - table.PrimaryKey("PK_PeopleLinks", x => x.ID); + table.PrimaryKey("PK_PeopleRoles", x => x.ID); table.ForeignKey( - name: "FK_PeopleLinks_People_PeopleID", + name: "FK_PeopleRoles_People_PeopleID", column: x => x.PeopleID, principalTable: "People", principalColumn: "ID", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "FK_PeopleLinks_Shows_ShowID", + name: "FK_PeopleRoles_Shows_ShowID", column: x => x.ShowID, principalTable: "Shows", principalColumn: "ID", @@ -500,13 +505,13 @@ namespace Kyoo.Models.DatabaseMigrations.Internal unique: true); migrationBuilder.CreateIndex( - name: "IX_PeopleLinks_PeopleID", - table: "PeopleLinks", + name: "IX_PeopleRoles_PeopleID", + table: "PeopleRoles", column: "PeopleID"); migrationBuilder.CreateIndex( - name: "IX_PeopleLinks_ShowID", - table: "PeopleLinks", + name: "IX_PeopleRoles_ShowID", + table: "PeopleRoles", column: "ShowID"); migrationBuilder.CreateIndex( @@ -569,7 +574,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal name: "MetadataIds"); migrationBuilder.DropTable( - name: "PeopleLinks"); + name: "PeopleRoles"); migrationBuilder.DropTable( name: "ProviderLinks"); diff --git a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs index 762ea73d..b7c0e3cf 100644 --- a/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs +++ b/Kyoo/Models/DatabaseMigrations/Internal/DatabaseContextModelSnapshot.cs @@ -1,5 +1,6 @@ // using System; +using System.Collections.Generic; using Kyoo; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -15,6 +16,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal { #pragma warning disable 612, 618 modelBuilder + .HasAnnotation("Npgsql:Enum:item_type", "show,movie,collection") + .HasAnnotation("Npgsql:Enum:status", "finished,airing,planned,unknown") + .HasAnnotation("Npgsql:Enum:stream_type", "unknow,video,audio,subtitle") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("ProductVersion", "3.1.3") .HasAnnotation("Relational:MaxIdentifierLength", 63); @@ -165,8 +169,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.Property("Name") .HasColumnType("text"); - b.Property("Paths") - .HasColumnType("text"); + b.Property>("Paths") + .HasColumnType("text[]"); b.Property("Slug") .HasColumnType("text"); @@ -260,10 +264,10 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("ImgPrimary") + b.Property("Name") .HasColumnType("text"); - b.Property("Name") + b.Property("Poster") .HasColumnType("text"); b.Property("Slug") @@ -302,7 +306,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal b.HasIndex("ShowID"); - b.ToTable("PeopleLinks"); + b.ToTable("PeopleRoles"); }); modelBuilder.Entity("Kyoo.Models.ProviderID", b => @@ -391,8 +395,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal .HasColumnType("integer") .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); - b.Property("Aliases") - .HasColumnType("text"); + b.Property>("Aliases") + .HasColumnType("text[]"); b.Property("Backdrop") .HasColumnType("text"); diff --git a/Kyoo/Views/API/PeopleAPI.cs b/Kyoo/Views/API/PeopleAPI.cs deleted file mode 100644 index ad27d8b9..00000000 --- a/Kyoo/Views/API/PeopleAPI.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Kyoo.Api -{ - [Route("api/[controller]")] - [ApiController] - public class PeopleController : ControllerBase - { - private readonly ILibraryManager _libraryManager; - - public PeopleController(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - [HttpGet("{peopleSlug}")] - [Authorize(Policy="Read")] - public async Task> GetPeople(string peopleSlug) - { - People people = await _libraryManager.GetPeople(peopleSlug); - - if (people == null) - return NotFound(); - return new Collection(people.Slug, people.Name, null, null) - { - Shows = people.Roles.Select(x => x.Show), - Poster = "peopleimg/" + people.Slug - }; - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs new file mode 100644 index 00000000..2803ead9 --- /dev/null +++ b/Kyoo/Views/API/PeopleApi.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/people")] + [ApiController] + public class PeopleApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public PeopleApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.PeopleRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{id:int}/role")] + [HttpGet("{id:int}/roles")] + [Authorize(Policy = "Read")] + [JsonDetailed] + public async Task>> GetRoles(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection resources = await _libraryManager.GetRolesFromPeople(id, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(resources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/role")] + [HttpGet("{slug}/roles")] + [Authorize(Policy = "Read")] + [JsonDetailed] + public async Task>> GetRoles(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetRolesFromPeople(slug, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/SearchAPI.cs b/Kyoo/Views/API/SearchApi.cs similarity index 80% rename from Kyoo/Views/API/SearchAPI.cs rename to Kyoo/Views/API/SearchApi.cs index 4efcf93a..20c4297d 100644 --- a/Kyoo/Views/API/SearchAPI.cs +++ b/Kyoo/Views/API/SearchApi.cs @@ -6,13 +6,13 @@ using Microsoft.AspNetCore.Mvc; namespace Kyoo.Api { - [Route("api/[controller]")] + [Route("api/search")] [ApiController] - public class SearchController : ControllerBase + public class SearchApi : ControllerBase { private readonly ILibraryManager _libraryManager; - public SearchController(ILibraryManager libraryManager) + public SearchApi(ILibraryManager libraryManager) { _libraryManager = libraryManager; } @@ -21,7 +21,7 @@ namespace Kyoo.Api [Authorize(Policy="Read")] public async Task> Search(string query) { - SearchResult result = new SearchResult + return new SearchResult { Query = query, Collections = await _libraryManager.SearchCollections(query), @@ -31,7 +31,6 @@ namespace Kyoo.Api Genres = await _libraryManager.SearchGenres(query), Studios = await _libraryManager.SearchStudios(query) }; - return result; } } } \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index de1b3b06..3dad4b29 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit de1b3b069aa4f920bd5248223eda151b1eedf541 +Subproject commit 3dad4b29d6565d08ef0bce1e450b5de65f6fac16 From 7e098b4005be982c0ecb4bfad5e4df6533431f4b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 5 Aug 2020 01:20:43 +0200 Subject: [PATCH 34/36] Creating the episode api --- Kyoo.Common/Controllers/ILibraryManager.cs | 54 +++++- Kyoo.Common/Controllers/IRepository.cs | 47 ++++- .../Implementations/LibraryManager.cs | 98 +++++++++- .../Repositories/EpisodeRepository.cs | 27 ++- .../Repositories/SeasonRepository.cs | 5 + .../Repositories/ShowRepository.cs | 10 + .../Repositories/TrackRepository.cs | 79 +++++++- Kyoo/Views/API/EpisodesAPI.cs | 43 ----- Kyoo/Views/API/EpisodesApi.cs | 173 ++++++++++++++++++ Kyoo/Views/API/SeasonApi.cs | 29 ++- Kyoo/Views/WebClient | 2 +- 11 files changed, 502 insertions(+), 65 deletions(-) delete mode 100644 Kyoo/Views/API/EpisodesAPI.cs create mode 100644 Kyoo/Views/API/EpisodesApi.cs diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index e79bb825..9abda1c4 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -23,6 +23,18 @@ namespace Kyoo.Controllers IGenreRepository GenreRepository { get; } IProviderRepository ProviderRepository { get; } + // Get by id + Task GetLibrary(int id); + Task GetCollection(int id); + Task GetShow(int id); + Task GetSeason(int id); + Task GetSeason(int showID, int seasonNumber); + Task GetEpisode(int id); + Task GetEpisode(int showID, int seasonNumber, int episodeNumber); + Task GetGenre(int id); + Task GetStudio(int id); + Task GetPeople(int id); + // Get by slug Task GetLibrary(string slug); Task GetCollection(string slug); @@ -31,7 +43,6 @@ namespace Kyoo.Controllers Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber); Task GetMovieEpisode(string movieSlug); Task GetTrack(int id); - Task GetTrack(int episodeID, string language, bool isForced); Task GetGenre(string slug); Task GetStudio(string slug); Task GetPeople(string slug); @@ -150,8 +161,49 @@ namespace Kyoo.Controllers Pagination limit = default ) => GetGenresFromShow(showSlug, where, new Sort(sort), limit); + Task> GetTracksFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(int episodeID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(episodeID, where, new Sort(sort), limit); + + Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(showID, seasonNumber, episodeNumber, where, new Sort(sort), limit); + + Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetTracksFromEpisode(showSlug, seasonNumber, episodeNumber, where, new Sort(sort), limit); + Task GetStudioFromShow(int showID); Task GetStudioFromShow(string showSlug); + Task GetShowFromSeason(int seasonID); + Task GetShowFromEpisode(int episodeID); + Task GetSeasonFromEpisode(int episodeID); Task> GetLibrariesFromShow(int showID, Expression> where = null, diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index 2c4f5c23..01877446 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -142,6 +142,9 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromCollection(slug, where, new Sort(sort), limit); + + Task GetFromSeason(int seasonID); + Task GetFromEpisode(int episodeID); } public interface ISeasonRepository : IRepository @@ -169,11 +172,17 @@ namespace Kyoo.Controllers Expression> sort, Pagination limit = default ) => GetFromShow(showSlug, where, new Sort(sort), limit); + + Task GetFromEpisode(int episodeID); } public interface IEpisodeRepository : IRepository { + Task Get(int showID, int seasonNumber, int episodeNumber); Task Get(string showSlug, int seasonNumber, int episodeNumber); + Task Get(int seasonID, int episodeNumber); + Task GetAbsolute(int showID, int absoluteNumber); + Task GetAbsolute(string showSlug, int absoluteNumber); Task Delete(string showSlug, int seasonNumber, int episodeNumber); Task> GetFromShow(int showID, @@ -231,7 +240,43 @@ namespace Kyoo.Controllers public interface ITrackRepository : IRepository { - Task Get(int episodeID, string languageTag, bool isForced); + Task> GetFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(int episodeID, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(episodeID, where, new Sort(sort), limit); + + Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(showID, seasonNumber, episodeNumber, where, new Sort(sort), limit); + + Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default); + Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [Optional] Expression> where, + Expression> sort, + Pagination limit = default + ) => GetFromEpisode(showSlug, seasonNumber, episodeNumber, where, new Sort(sort), limit); } public interface ILibraryRepository : IRepository diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index db85d358..cb926f21 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -75,6 +75,56 @@ namespace Kyoo.Controllers ); } + public Task GetLibrary(int id) + { + return LibraryRepository.Get(id); + } + + public Task GetCollection(int id) + { + return CollectionRepository.Get(id); + } + + public Task GetShow(int id) + { + return ShowRepository.Get(id); + } + + public Task GetSeason(int id) + { + return SeasonRepository.Get(id); + } + + public Task GetSeason(int showID, int seasonNumber) + { + return SeasonRepository.Get(showID, seasonNumber); + } + + public Task GetEpisode(int id) + { + return EpisodeRepository.Get(id); + } + + public Task GetEpisode(int showID, int seasonNumber, int episodeNumber) + { + return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); + } + + public Task GetGenre(int id) + { + return GenreRepository.Get(id); + } + + public Task GetStudio(int id) + { + return StudioRepository.Get(id); + } + + public Task GetPeople(int id) + { + return PeopleRepository.Get(id); + } + public Task GetLibrary(string slug) { return LibraryRepository.Get(slug); @@ -109,11 +159,6 @@ namespace Kyoo.Controllers { return TrackRepository.Get(id); } - - public Task GetTrack(int episodeID, string language, bool isForced) - { - return TrackRepository.Get(episodeID, language, isForced); - } public Task GetGenre(string slug) { @@ -290,6 +335,34 @@ namespace Kyoo.Controllers return GenreRepository.GetFromShow(showSlug, where, sort, limit); } + public Task> GetTracksFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(episodeID, where, sort, limit); + } + + public Task> GetTracksFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(showID, seasonNumber, episodeNumber, where, sort, limit); + } + + public Task> GetTracksFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + return TrackRepository.GetFromEpisode(showSlug, seasonNumber, episodeNumber, where, sort, limit); + } + public Task GetStudioFromShow(int showID) { return StudioRepository.GetFromShow(showID); @@ -300,6 +373,21 @@ namespace Kyoo.Controllers return StudioRepository.GetFromShow(showSlug); } + public Task GetShowFromSeason(int seasonID) + { + return ShowRepository.GetFromSeason(seasonID); + } + + public Task GetShowFromEpisode(int episodeID) + { + return ShowRepository.GetFromEpisode(episodeID); + } + + public Task GetSeasonFromEpisode(int episodeID) + { + return SeasonRepository.GetFromEpisode(episodeID); + } + public Task> GetLibrariesFromShow(int showID, Expression> where = null, Sort sort = default, diff --git a/Kyoo/Controllers/Repositories/EpisodeRepository.cs b/Kyoo/Controllers/Repositories/EpisodeRepository.cs index bf8fa6a9..9c0676e4 100644 --- a/Kyoo/Controllers/Repositories/EpisodeRepository.cs +++ b/Kyoo/Controllers/Repositories/EpisodeRepository.cs @@ -61,7 +61,32 @@ namespace Kyoo.Controllers && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } - + + public Task Get(int showID, int seasonNumber, int episodeNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); + } + + public Task Get(int seasonID, int episodeNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.SeasonID == seasonID + && x.EpisodeNumber == episodeNumber); + } + + public Task GetAbsolute(int showID, int absoluteNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID + && x.AbsoluteNumber == absoluteNumber); + } + + public Task GetAbsolute(string showSlug, int absoluteNumber) + { + return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug + && x.AbsoluteNumber == absoluteNumber); + } + public override async Task> Search(string query) { return await _database.Episodes diff --git a/Kyoo/Controllers/Repositories/SeasonRepository.cs b/Kyoo/Controllers/Repositories/SeasonRepository.cs index d48c340e..4875ed85 100644 --- a/Kyoo/Controllers/Repositories/SeasonRepository.cs +++ b/Kyoo/Controllers/Repositories/SeasonRepository.cs @@ -155,5 +155,10 @@ namespace Kyoo.Controllers if (obj.Episodes != null) await _episodes.Value.DeleteRange(obj.Episodes); } + + public Task GetFromEpisode(int episodeID) + { + return _database.Seasons.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index 9ea7906c..a23d6c35 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -244,5 +244,15 @@ namespace Kyoo.Controllers throw new ItemNotFound(); return shows; } + + public Task GetFromSeason(int seasonID) + { + return _database.Shows.FirstOrDefaultAsync(x => x.Seasons.Any(y => y.ID == seasonID)); + } + + public Task GetFromEpisode(int episodeID) + { + return _database.Shows.FirstOrDefaultAsync(x => x.Episodes.Any(y => y.ID == episodeID)); + } } } \ No newline at end of file diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index 805d5736..f6c81f70 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -1,22 +1,41 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading.Tasks; using Kyoo.Models; +using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers { public class TrackRepository : LocalRepository, ITrackRepository { private readonly DatabaseContext _database; + private readonly Lazy _episodes; protected override Expression> DefaultSort => x => x.ID; - public TrackRepository(DatabaseContext database) : base(database) + public TrackRepository(DatabaseContext database, IServiceProvider services) : base(database) { _database = database; + _episodes = new Lazy(services.GetRequiredService); + } + + public override void Dispose() + { + _database.Dispose(); + if (_episodes.IsValueCreated) + _episodes.Value.Dispose(); + } + + public override async ValueTask DisposeAsync() + { + await _database.DisposeAsync(); + if (_episodes.IsValueCreated) + await _episodes.Value.DisposeAsync(); } public override Task Get(string slug) @@ -42,14 +61,6 @@ namespace Kyoo.Controllers && x.Language == language && x.IsForced == forced); } - - public Task Get(int episodeID, string languageTag, bool isForced) - { - return _database.Tracks.FirstOrDefaultAsync(x => x.EpisodeID == episodeID - && x.Language == languageTag - && x.IsForced == isForced); - } - public override Task> Search(string query) { throw new InvalidOperationException("Tracks do not support the search method."); @@ -82,5 +93,55 @@ namespace Kyoo.Controllers _database.Entry(obj).State = EntityState.Deleted; await _database.SaveChangesAsync(); } + + public async Task> GetFromEpisode(int episodeID, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.EpisodeID == episodeID), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(episodeID) == null) + throw new ItemNotFound(); + return tracks; + } + + public async Task> GetFromEpisode(int showID, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.Episode.ShowID == showID + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(showID, seasonNumber, episodeNumber) == null) + throw new ItemNotFound(); + return tracks; + } + + public async Task> GetFromEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + Expression> where = null, + Sort sort = default, + Pagination limit = default) + { + ICollection tracks = await ApplyFilters(_database.Tracks.Where(x => x.Episode.Show.Slug == showSlug + && x.Episode.SeasonNumber == seasonNumber + && x.Episode.EpisodeNumber == episodeNumber), + where, + sort, + limit); + if (!tracks.Any() && await _episodes.Value.Get(showSlug, seasonNumber, episodeNumber) == null) + throw new ItemNotFound(); + return tracks; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/EpisodesAPI.cs b/Kyoo/Views/API/EpisodesAPI.cs deleted file mode 100644 index 736ffb02..00000000 --- a/Kyoo/Views/API/EpisodesAPI.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Microsoft.AspNetCore.Authorization; - -namespace Kyoo.Api -{ - [Route("api/[controller]")] - [ApiController] - public class EpisodesController : ControllerBase - { - private readonly ILibraryManager _libraryManager; - - public EpisodesController(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - [HttpGet("{showSlug}/season/{seasonNumber}")] - [Authorize(Policy="Read")] - public Task>> GetEpisodesForSeason(string showSlug, int seasonNumber) - { - throw new NotImplementedException(); - } - - [HttpGet("{showSlug}/season/{seasonNumber}/episode/{episodeNumber}")] - [Authorize(Policy="Read")] - [JsonDetailed] - public async Task> GetEpisode(string showSlug, int seasonNumber, int episodeNumber) - { - Episode episode = await _libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); - - if (episode == null) - return NotFound(); - - return episode; - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/EpisodesApi.cs b/Kyoo/Views/API/EpisodesApi.cs new file mode 100644 index 00000000..8dd9fa92 --- /dev/null +++ b/Kyoo/Views/API/EpisodesApi.cs @@ -0,0 +1,173 @@ +using System; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/episode")] + [Route("api/episodes")] + [ApiController] + public class EpisodesApi : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public EpisodesApi(ILibraryManager libraryManager, IConfiguration configuration) + : base(libraryManager.EpisodeRepository, configuration) + { + _libraryManager = libraryManager; + } + + [HttpGet("{episodeID:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int episodeID) + { + return await _libraryManager.GetShowFromEpisode(episodeID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(string showSlug) + { + return await _libraryManager.GetShow(showSlug); + } + + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int showID, int _) + { + return await _libraryManager.GetShow(showID); + } + + [HttpGet("{episodeID:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(int episodeID) + { + return await _libraryManager.GetSeasonFromEpisode(episodeID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(string showSlug, int seasonNuber) + { + return await _libraryManager.GetSeason(showSlug, seasonNuber); + } + + [HttpGet("{showID:int}-{seasonNumber:int}e{episodeNumber:int}/season")] + [Authorize(Policy = "Read")] + public async Task> GetSeason(int showID, int seasonNumber) + { + return await _libraryManager.GetSeason(showID, seasonNumber); + } + + [HttpGet("{episodeID:int}/track")] + [HttpGet("{episodeID:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(int episodeID, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(episodeID, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/track")] + [HttpGet("{showID:int}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(int showID, + int seasonNumber, + int episodeNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(showID, + seasonNumber, + episodeNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/track")] + [HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/tracks")] + [Authorize(Policy = "Read")] + public async Task>> GetEpisode(string showSlug, + int seasonNumber, + int episodeNumber, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 30) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetTracksFromEpisode(showSlug, + seasonNumber, + episodeNumber, + ApiHelper.ParseWhere(where), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index c7da45d9..5825b370 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -56,8 +56,8 @@ namespace Kyoo.Api } } - [HttpGet("{showSlug}-{seasonNumber:int}/episode")] - [HttpGet("{showSlug}-{seasonNumber:int}/episodes")] + [HttpGet("{showSlug}-s{seasonNumber:int}/episode")] + [HttpGet("{showSlug}-s{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] public async Task>> GetEpisode(string showSlug, int seasonNumber, @@ -90,8 +90,8 @@ namespace Kyoo.Api } } - [HttpGet("{showID:int}-{seasonNumber:int}/episode")] - [HttpGet("{showID:int}-{seasonNumber:int}/episodes")] + [HttpGet("{showID:int}-s{seasonNumber:int}/episode")] + [HttpGet("{showID:int}-s{seasonNumber:int}/episodes")] [Authorize(Policy = "Read")] public async Task>> GetEpisode(int showID, int seasonNumber, @@ -123,5 +123,26 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{seasonID:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int seasonID) + { + return await _libraryManager.GetShowFromSeason(seasonID); + } + + [HttpGet("{showSlug}-s{seasonNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(string showSlug, int _) + { + return await _libraryManager.GetShow(showSlug); + } + + [HttpGet("{showID:int}-s{seasonNumber:int}/show")] + [Authorize(Policy = "Read")] + public async Task> GetShow(int showID, int _) + { + return await _libraryManager.GetShow(showID); + } } } \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 3dad4b29..7e8c7420 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 3dad4b29d6565d08ef0bce1e450b5de65f6fac16 +Subproject commit 7e8c74206356587b06272c5dd7c22fa09420d421 From 83a463851d95f6f1c5efcecf44a05f6e6c382165 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 6 Aug 2020 17:35:41 +0200 Subject: [PATCH 35/36] Creating the genre API (using the unimplemented contain api) --- Kyoo.CommonAPI/ApiHelper.cs | 58 +++++++++++++++++++++++----- Kyoo/Views/API/GenresAPI.cs | 75 ++++++++++++++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/Kyoo.CommonAPI/ApiHelper.cs b/Kyoo.CommonAPI/ApiHelper.cs index 7486b163..8258cbe6 100644 --- a/Kyoo.CommonAPI/ApiHelper.cs +++ b/Kyoo.CommonAPI/ApiHelper.cs @@ -20,13 +20,14 @@ namespace Kyoo.CommonApi return operand(left, right); } - public static Expression> ParseWhere(Dictionary where) + public static Expression> ParseWhere(Dictionary where, + Expression> defaultWhere = null) { if (where == null || where.Count == 0) return null; ParameterExpression param = Expression.Parameter(typeof(T)); - Expression expression = null; + Expression expression = defaultWhere?.Body; foreach ((string key, string desired) in where) { @@ -42,21 +43,26 @@ namespace Kyoo.CommonApi 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); + + ConstantExpression valueExpr = null; + if (operand != "ctn") + { + Type propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; + object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase) + ? null + : Convert.ChangeType(value, propertyType); + valueExpr = Expression.Constant(val, property.PropertyType); + } Expression condition = operand switch { - "eq" => Expression.Equal(propertyExpr, valueExpr), - "not" => Expression.NotEqual(propertyExpr, valueExpr), + "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), + "ctn" => ContainsResourceExpression(propertyExpr, value), _ => throw new ArgumentException($"Invalid operand: {operand}") }; @@ -68,5 +74,37 @@ namespace Kyoo.CommonApi return Expression.Lambda>(expression!, param); } + + private static Expression ContainsResourceExpression(MemberExpression xProperty, string value) + { + // x => x.PROPERTY.Any(y => y.Slug == value) + Expression ret = null; + ParameterExpression y = Expression.Parameter(xProperty.Type.GenericTypeArguments.First(), "y"); + foreach (string val in value.Split(',')) + { + MemberExpression yProperty; + ConstantExpression yValue; + if (int.TryParse(val, out int id)) + { + yProperty = Expression.Property(y, "ID"); + yValue = Expression.Constant(id); + } + else + { + yProperty = Expression.Property(y, "Slug"); + yValue = Expression.Constant(val); + } + + LambdaExpression lambda = Expression.Lambda(Expression.Equal(yProperty, yValue), y); + Expression iteration = Expression.Call(typeof(Enumerable), "Any", xProperty.Type.GenericTypeArguments, + xProperty, lambda); + + if (ret == null) + ret = iteration; + else + ret = Expression.AndAlso(ret, iteration); + } + return ret; + } } } \ No newline at end of file diff --git a/Kyoo/Views/API/GenresAPI.cs b/Kyoo/Views/API/GenresAPI.cs index 118c4fcc..f0ded1e3 100644 --- a/Kyoo/Views/API/GenresAPI.cs +++ b/Kyoo/Views/API/GenresAPI.cs @@ -1,27 +1,92 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; namespace Kyoo.Api { - [Route("api/genres")] [Route("api/genre")] + [Route("api/genres")] [ApiController] - public class GenresAPI : ControllerBase + public class GenresAPI : CrudApi { private readonly ILibraryManager _libraryManager; - public GenresAPI(ILibraryManager libraryManager) + public GenresAPI(ILibraryManager libraryManager, IConfiguration config) + : base(libraryManager.GenreRepository, config) { _libraryManager = libraryManager; } - public async Task>> Index() + [HttpGet("{id:int}/show")] + [HttpGet("{id:int}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) { - return (await _libraryManager.GetGenres()).ToList(); + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.ID == id)), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/show")] + [HttpGet("{slug}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.Genres.Any(y => y.Slug == slug)), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } } } } \ No newline at end of file From 49471e013d17ec53df92267fbb28fa0a11efa492 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 7 Aug 2020 00:00:46 +0200 Subject: [PATCH 36/36] Cleaning up --- .../API/{EpisodesApi.cs => EpisodeApi.cs} | 4 +- Kyoo/Views/API/{GenresAPI.cs => GenreApi.cs} | 4 +- .../API/{LibrariesApi.cs => LibraryApi.cs} | 4 +- .../{LibraryItemsApi.cs => LibraryItemApi.cs} | 4 +- Kyoo/Views/API/ProviderAPI.cs | 27 ------ Kyoo/Views/API/ProviderApi.cs | 24 +++++ Kyoo/Views/API/{ShowsApi.cs => ShowApi.cs} | 4 +- Kyoo/Views/API/StudioAPI.cs | 27 ------ Kyoo/Views/API/StudioApi.cs | 91 +++++++++++++++++++ Kyoo/Views/WebClient | 2 +- 10 files changed, 126 insertions(+), 65 deletions(-) rename Kyoo/Views/API/{EpisodesApi.cs => EpisodeApi.cs} (97%) rename Kyoo/Views/API/{GenresAPI.cs => GenreApi.cs} (94%) rename Kyoo/Views/API/{LibrariesApi.cs => LibraryApi.cs} (97%) rename Kyoo/Views/API/{LibraryItemsApi.cs => LibraryItemApi.cs} (91%) delete mode 100644 Kyoo/Views/API/ProviderAPI.cs create mode 100644 Kyoo/Views/API/ProviderApi.cs rename Kyoo/Views/API/{ShowsApi.cs => ShowApi.cs} (98%) delete mode 100644 Kyoo/Views/API/StudioAPI.cs create mode 100644 Kyoo/Views/API/StudioApi.cs diff --git a/Kyoo/Views/API/EpisodesApi.cs b/Kyoo/Views/API/EpisodeApi.cs similarity index 97% rename from Kyoo/Views/API/EpisodesApi.cs rename to Kyoo/Views/API/EpisodeApi.cs index 8dd9fa92..af29a341 100644 --- a/Kyoo/Views/API/EpisodesApi.cs +++ b/Kyoo/Views/API/EpisodeApi.cs @@ -14,11 +14,11 @@ namespace Kyoo.Api [Route("api/episode")] [Route("api/episodes")] [ApiController] - public class EpisodesApi : CrudApi + public class EpisodeApi : CrudApi { private readonly ILibraryManager _libraryManager; - public EpisodesApi(ILibraryManager libraryManager, IConfiguration configuration) + public EpisodeApi(ILibraryManager libraryManager, IConfiguration configuration) : base(libraryManager.EpisodeRepository, configuration) { _libraryManager = libraryManager; diff --git a/Kyoo/Views/API/GenresAPI.cs b/Kyoo/Views/API/GenreApi.cs similarity index 94% rename from Kyoo/Views/API/GenresAPI.cs rename to Kyoo/Views/API/GenreApi.cs index f0ded1e3..10f15922 100644 --- a/Kyoo/Views/API/GenresAPI.cs +++ b/Kyoo/Views/API/GenreApi.cs @@ -15,11 +15,11 @@ namespace Kyoo.Api [Route("api/genre")] [Route("api/genres")] [ApiController] - public class GenresAPI : CrudApi + public class GenreApi : CrudApi { private readonly ILibraryManager _libraryManager; - public GenresAPI(ILibraryManager libraryManager, IConfiguration config) + public GenreApi(ILibraryManager libraryManager, IConfiguration config) : base(libraryManager.GenreRepository, config) { _libraryManager = libraryManager; diff --git a/Kyoo/Views/API/LibrariesApi.cs b/Kyoo/Views/API/LibraryApi.cs similarity index 97% rename from Kyoo/Views/API/LibrariesApi.cs rename to Kyoo/Views/API/LibraryApi.cs index d7786348..86b00a66 100644 --- a/Kyoo/Views/API/LibrariesApi.cs +++ b/Kyoo/Views/API/LibraryApi.cs @@ -14,12 +14,12 @@ namespace Kyoo.Api [Route("api/library")] [Route("api/libraries")] [ApiController] - public class LibrariesAPI : CrudApi + public class LibraryAPI : CrudApi { private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; - public LibrariesAPI(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) + public LibraryAPI(ILibraryManager libraryManager, ITaskManager taskManager, IConfiguration configuration) : base(libraryManager.LibraryRepository, configuration) { _libraryManager = libraryManager; diff --git a/Kyoo/Views/API/LibraryItemsApi.cs b/Kyoo/Views/API/LibraryItemApi.cs similarity index 91% rename from Kyoo/Views/API/LibraryItemsApi.cs rename to Kyoo/Views/API/LibraryItemApi.cs index 9a32dade..e5918a3c 100644 --- a/Kyoo/Views/API/LibraryItemsApi.cs +++ b/Kyoo/Views/API/LibraryItemApi.cs @@ -15,13 +15,13 @@ namespace Kyoo.Api [Route("api/item")] [Route("api/items")] [ApiController] - public class LibraryItemsApi : ControllerBase + public class LibraryItemApi : ControllerBase { private readonly ILibraryItemRepository _libraryItems; private readonly string _baseURL; - public LibraryItemsApi(ILibraryItemRepository libraryItems, IConfiguration configuration) + public LibraryItemApi(ILibraryItemRepository libraryItems, IConfiguration configuration) { _libraryItems = libraryItems; _baseURL = configuration.GetValue("public_url").TrimEnd('/'); diff --git a/Kyoo/Views/API/ProviderAPI.cs b/Kyoo/Views/API/ProviderAPI.cs deleted file mode 100644 index d1390e2c..00000000 --- a/Kyoo/Views/API/ProviderAPI.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using Kyoo.Models; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Kyoo.Api -{ - [Route("api/provider")] - [Route("api/providers")] - [ApiController] - public class ProviderAPI : ControllerBase - { - private readonly DatabaseContext _database; - - public ProviderAPI(DatabaseContext database) - { - _database = database; - } - - [HttpGet("")] - [Authorize(Policy="Read")] - public ActionResult> Index() - { - return _database.Providers; - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/ProviderApi.cs b/Kyoo/Views/API/ProviderApi.cs new file mode 100644 index 00000000..c2798434 --- /dev/null +++ b/Kyoo/Views/API/ProviderApi.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/provider")] + [Route("api/providers")] + [ApiController] + public class ProviderAPI : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public ProviderAPI(ILibraryManager libraryManager, IConfiguration config) + : base(libraryManager.ProviderRepository, config) + { + _libraryManager = libraryManager; + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/API/ShowsApi.cs b/Kyoo/Views/API/ShowApi.cs similarity index 98% rename from Kyoo/Views/API/ShowsApi.cs rename to Kyoo/Views/API/ShowApi.cs index 9f987e66..e5af30a7 100644 --- a/Kyoo/Views/API/ShowsApi.cs +++ b/Kyoo/Views/API/ShowApi.cs @@ -14,11 +14,11 @@ namespace Kyoo.Api [Route("api/show")] [Route("api/shows")] [ApiController] - public class ShowsApi : CrudApi + public class ShowApi : CrudApi { private readonly ILibraryManager _libraryManager; - public ShowsApi(ILibraryManager libraryManager, IConfiguration configuration) + public ShowApi(ILibraryManager libraryManager, IConfiguration configuration) : base(libraryManager.ShowRepository, configuration) { _libraryManager = libraryManager; diff --git a/Kyoo/Views/API/StudioAPI.cs b/Kyoo/Views/API/StudioAPI.cs deleted file mode 100644 index 88efca86..00000000 --- a/Kyoo/Views/API/StudioAPI.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Kyoo.Controllers; -using Kyoo.Models; -using Microsoft.AspNetCore.Mvc; - -namespace Kyoo.Api -{ - [Route("api/studios")] - [Route("api/studio")] - [ApiController] - public class StudioAPI : ControllerBase - { - private readonly ILibraryManager _libraryManager; - - public StudioAPI(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - public async Task>> Index() - { - return (await _libraryManager.GetStudios()).ToList(); - } - } -} \ No newline at end of file diff --git a/Kyoo/Views/API/StudioApi.cs b/Kyoo/Views/API/StudioApi.cs new file mode 100644 index 00000000..8c15be4a --- /dev/null +++ b/Kyoo/Views/API/StudioApi.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Kyoo.CommonApi; +using Kyoo.Controllers; +using Kyoo.Models; +using Kyoo.Models.Exceptions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace Kyoo.Api +{ + [Route("api/studio")] + [Route("api/studios")] + [ApiController] + public class StudioAPI : CrudApi + { + private readonly ILibraryManager _libraryManager; + + public StudioAPI(ILibraryManager libraryManager, IConfiguration config) + : base(libraryManager.StudioRepository, config) + { + _libraryManager = libraryManager; + } + + [HttpGet("{id:int}/show")] + [HttpGet("{id:int}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(int id, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.StudioID == id), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + + [HttpGet("{slug}/show")] + [HttpGet("{slug}/shows")] + [Authorize(Policy = "Read")] + public async Task>> GetShows(string slug, + [FromQuery] string sortBy, + [FromQuery] int afterID, + [FromQuery] Dictionary where, + [FromQuery] int limit = 20) + { + where.Remove("sortBy"); + where.Remove("limit"); + where.Remove("afterID"); + + try + { + ICollection ressources = await _libraryManager.GetShows( + ApiHelper.ParseWhere(where, x => x.Studio.Slug == slug), + new Sort(sortBy), + new Pagination(limit, afterID)); + + return Page(ressources, limit); + } + catch (ItemNotFound) + { + return NotFound(); + } + catch (ArgumentException ex) + { + return BadRequest(new {Error = ex.Message}); + } + } + } +} \ No newline at end of file diff --git a/Kyoo/Views/WebClient b/Kyoo/Views/WebClient index 7e8c7420..9e7e7a10 160000 --- a/Kyoo/Views/WebClient +++ b/Kyoo/Views/WebClient @@ -1 +1 @@ -Subproject commit 7e8c74206356587b06272c5dd7c22fa09420d421 +Subproject commit 9e7e7a1093d85f8e980b8115e0514166701cbd6b