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}")]