Making where & pagination works

This commit is contained in:
Zoe Roux 2020-07-05 18:29:16 +02:00
parent 4e9096ae76
commit 6bdd363b7b
6 changed files with 99 additions and 24 deletions

View File

@ -25,11 +25,14 @@ namespace Kyoo.Controllers
{ {
public Expression<Func<T, object>> Key { get; } public Expression<Func<T, object>> Key { get; }
public bool Descendant { get; } public bool Descendant { get; }
public Sort(Expression<Func<T, object>> key, bool descendant = false) public Sort(Expression<Func<T, object>> key, bool descendant = false)
{ {
Key = key; Key = key;
Descendant = descendant; Descendant = descendant;
if (!(Key.Body is MemberExpression))
throw new ArgumentException("The given sort key is not valid.");
} }
public Sort(string sortBy) public Sort(string sortBy)

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Kyoo.Models
{
public class Page<T>
{
public string This { get; set; }
public string First { get; set; }
public string Next { get; set; }
public int Count => Items.Count;
public ICollection<T> Items { get; set; }
public Page() { }
public Page(ICollection<T> items)
{
Items = items;
}
public Page(ICollection<T> items, string @this, string next, string first)
{
Items = items;
This = @this;
Next = next;
First = first;
}
public Page(ICollection<T> items,
Func<T, string> getID,
string url,
Dictionary<string, string> 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();
}
}
}

View File

@ -276,7 +276,7 @@ namespace Kyoo
object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase) object val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase)
? null ? null
: Convert.ChangeType(value, propertyType); : Convert.ChangeType(value, propertyType);
ConstantExpression valueExpr = Expression.Constant(val); ConstantExpression valueExpr = Expression.Constant(val, property.PropertyType);
Expression condition = operand switch Expression condition = operand switch
{ {
@ -297,7 +297,14 @@ namespace Kyoo
expression = condition; expression = condition;
} }
return Expression.Lambda<Func<T, bool>>(expression!); return Expression.Lambda<Func<T, bool>>(expression!, param);
}
public static string ToQueryString(this Dictionary<string, string> query)
{
if (!query.Any())
return string.Empty;
return "?" + string.Join('&', query.Select(x => $"{x.Key}={x.Value}"));
} }
} }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
@ -39,20 +40,13 @@ namespace Kyoo.Controllers
public Task<Episode> Get(string slug) public Task<Episode> Get(string slug)
{ {
int sIndex = slug.IndexOf("-s", StringComparison.Ordinal); Match match = Regex.Match(slug, @"(<show>.*)-s(<season>\d*)-e(<episode>\d*)");
int eIndex = slug.IndexOf("-e", StringComparison.Ordinal);
if (sIndex == -1 && eIndex == -1)
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug);
if (sIndex == -1 || eIndex == -1 || eIndex < sIndex) if (!match.Success)
throw new InvalidOperationException("Invalid episode slug. Format: {showSlug}-s{seasonNumber}-e{episodeNumber}"); return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == slug);
string showSlug = slug.Substring(0, sIndex); return Get(match.Groups["show"].Value,
if (!int.TryParse(slug.Substring(sIndex + 2), out int seasonNumber)) int.Parse(match.Groups["season"].Value),
throw new InvalidOperationException("Invalid episode slug. Format: {showSlug}-s{seasonNumber}-e{episodeNumber}"); int.Parse(match.Groups["episode"].Value));
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);
} }
public Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber) public Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber)

View File

@ -92,7 +92,7 @@ namespace Kyoo.Controllers
(ParameterExpression)((MemberExpression)sortKey.Body).Expression (ParameterExpression)((MemberExpression)sortKey.Body).Expression
)); ));
} }
query = query.Take(page.Count <= 0 ? 20 : page.Count); query = query.Take(page.Count);
return await query.ToListAsync(); return await query.ToListAsync();
} }

View File

@ -1,4 +1,5 @@
using Kyoo.Models; using System;
using Kyoo.Models;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -6,6 +7,7 @@ using System.Threading.Tasks;
using Kyoo.Controllers; using Kyoo.Controllers;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace Kyoo.Api namespace Kyoo.Api
{ {
@ -19,23 +21,26 @@ namespace Kyoo.Api
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IThumbnailsManager _thumbnailsManager; private readonly IThumbnailsManager _thumbnailsManager;
private readonly ITaskManager _taskManager; private readonly ITaskManager _taskManager;
private readonly string _baseURL;
public ShowsAPI(ILibraryManager libraryManager, public ShowsAPI(ILibraryManager libraryManager,
IProviderManager providerManager, IProviderManager providerManager,
DatabaseContext database, DatabaseContext database,
IThumbnailsManager thumbnailsManager, IThumbnailsManager thumbnailsManager,
ITaskManager taskManager) ITaskManager taskManager,
IConfiguration configuration)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_providerManager = providerManager; _providerManager = providerManager;
_database = database; _database = database;
_thumbnailsManager = thumbnailsManager; _thumbnailsManager = thumbnailsManager;
_taskManager = taskManager; _taskManager = taskManager;
_baseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
} }
[HttpGet] [HttpGet]
[Authorize(Policy="Read")] [Authorize(Policy="Read")]
public async Task<IEnumerable<Show>> GetShows([FromQuery] string sortBy, public async Task<ActionResult<Page<Show>>> GetShows([FromQuery] string sortBy,
[FromQuery] int limit, [FromQuery] int limit,
[FromQuery] int afterID, [FromQuery] int afterID,
[FromQuery] Dictionary<string, string> where) [FromQuery] Dictionary<string, string> where)
@ -43,10 +48,26 @@ namespace Kyoo.Api
where.Remove("sortBy"); where.Remove("sortBy");
where.Remove("limit"); where.Remove("limit");
where.Remove("afterID"); where.Remove("afterID");
if (limit <= 0)
return await _libraryManager.GetShows(Utility.ParseWhere<Show>(where), limit = 20;
new Sort<Show>(sortBy),
new Pagination(limit, afterID)); ICollection<Show> shows;
try
{
shows = await _libraryManager.GetShows(Utility.ParseWhere<Show>(where),
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
}
catch (ArgumentException ex)
{
return BadRequest(new { Error = ex.Message });
}
return new Page<Show>(shows,
x => $"{x.ID}",
_baseURL + Request.Path,
Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase),
limit);
} }
[HttpGet("{slug}")] [HttpGet("{slug}")]