mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add seed in random queries next url
This commit is contained in:
parent
76d0c53cc1
commit
97de98b89a
@ -42,12 +42,12 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <summary>
|
||||
/// The link of the previous page.
|
||||
/// </summary>
|
||||
public string Previous { get; }
|
||||
public string? Previous { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The link of the next page.
|
||||
/// </summary>
|
||||
public string Next { get; }
|
||||
public string? Next { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of items in the current page.
|
||||
|
@ -17,6 +17,7 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
@ -57,7 +58,7 @@ namespace Kyoo.Abstractions.Controllers
|
||||
public record Conglomerate(params Sort<T>[] List) : Sort<T>;
|
||||
|
||||
/// <summary>Sort randomly items</summary>
|
||||
public record Random(int seed) : Sort<T>;
|
||||
public record Random(uint seed) : Sort<T>;
|
||||
|
||||
/// <summary>The default sort method for the given type.</summary>
|
||||
public record Default : Sort<T>;
|
||||
@ -66,19 +67,20 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
|
||||
/// </summary>
|
||||
/// <param name="sortBy">A key name with an optional order specifier. Format: "key:asc", "key:desc" or "key".</param>
|
||||
/// <param name="seed">The random seed.</param>
|
||||
/// <exception cref="ArgumentException">An invalid key or sort specifier as been given.</exception>
|
||||
/// <returns>A <see cref="Sort{T}"/> for the given string</returns>
|
||||
public static Sort<T> From(string sortBy)
|
||||
public static Sort<T> From(string? sortBy, uint seed)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sortBy) || sortBy == "default")
|
||||
return new Default();
|
||||
if (sortBy == "random")
|
||||
return new Random(new System.Random().Next(int.MinValue, int.MaxValue));
|
||||
return new Random(seed);
|
||||
if (sortBy.Contains(','))
|
||||
return new Conglomerate(sortBy.Split(',').Select(From).ToArray());
|
||||
return new Conglomerate(sortBy.Split(',').Select(x => From(x, seed)).ToArray());
|
||||
|
||||
if (sortBy.StartsWith("random:"))
|
||||
return new Random(int.Parse(sortBy["random:".Length..]));
|
||||
return new Random(uint.Parse(sortBy["random:".Length..]));
|
||||
|
||||
string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy;
|
||||
string? order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null;
|
||||
@ -87,11 +89,11 @@ namespace Kyoo.Abstractions.Controllers
|
||||
"desc" => true,
|
||||
"asc" => false,
|
||||
null => false,
|
||||
_ => throw new ArgumentException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
|
||||
_ => throw new ValidationException($"The sort order, if set, should be :asc or :desc but it was :{order}.")
|
||||
};
|
||||
PropertyInfo? property = typeof(T).GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
||||
if (property == null)
|
||||
throw new ArgumentException("The given sort key is not valid.");
|
||||
throw new ValidationException("The given sort key is not valid.");
|
||||
return new By(property.Name, desendant);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ using Autofac;
|
||||
using Kyoo.Abstractions;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Core.Api;
|
||||
using Kyoo.Core.Controllers;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -69,6 +70,7 @@ namespace Kyoo.Core
|
||||
services.AddMvcCore(options =>
|
||||
{
|
||||
options.Filters.Add<ExceptionFilter>();
|
||||
options.ModelBinderProviders.Insert(0, new SortBinder.Provider());
|
||||
})
|
||||
.AddNewtonsoftJson(x =>
|
||||
{
|
||||
|
@ -17,6 +17,7 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@ -50,6 +51,9 @@ namespace Kyoo.Core
|
||||
case ArgumentException ex:
|
||||
context.Result = new BadRequestObjectResult(new RequestError(ex.Message));
|
||||
break;
|
||||
case ValidationException ex:
|
||||
context.Result = new BadRequestObjectResult(new RequestError(ex.Message));
|
||||
break;
|
||||
case ItemNotFoundException ex:
|
||||
context.Result = new NotFoundObjectResult(new RequestError(ex.Message));
|
||||
break;
|
||||
|
@ -19,6 +19,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -42,14 +43,23 @@ namespace Kyoo.Core.Api
|
||||
protected Page<TResult> Page<TResult>(ICollection<TResult> resources, int limit)
|
||||
where TResult : IResource
|
||||
{
|
||||
Dictionary<string, string> query = Request.Query.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.Value.ToString(),
|
||||
StringComparer.InvariantCultureIgnoreCase
|
||||
);
|
||||
|
||||
// If the query was sorted randomly, add the seed to the url to get reproducible links (next,prev,first...)
|
||||
if (query.ContainsKey("sortBy"))
|
||||
{
|
||||
object seed = HttpContext.Items["seed"]!;
|
||||
|
||||
query["sortBy"] = Regex.Replace(query["sortBy"], "random(?!:)", $"random:{seed}");
|
||||
}
|
||||
return new Page<TResult>(
|
||||
resources,
|
||||
Request.Path,
|
||||
Request.Query.ToDictionary(
|
||||
x => x.Key,
|
||||
x => x.Value.ToString(),
|
||||
StringComparer.InvariantCultureIgnoreCase
|
||||
),
|
||||
query,
|
||||
limit
|
||||
);
|
||||
}
|
||||
|
@ -113,13 +113,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<Page<T>>> GetAll(
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<T> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<T> resources = await Repository.GetAll(
|
||||
ApiHelper.ParseWhere<T>(where),
|
||||
Sort<T>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
65
back/src/Kyoo.Core/Views/Helper/SortBinder.cs
Normal file
65
back/src/Kyoo.Core/Views/Helper/SortBinder.cs
Normal file
@ -0,0 +1,65 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
|
||||
namespace Kyoo.Core.Api;
|
||||
|
||||
public class SortBinder : IModelBinder
|
||||
{
|
||||
private readonly Random _rng = new();
|
||||
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
ValueProviderResult sortBy = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
|
||||
uint seed = BitConverter.ToUInt32(
|
||||
BitConverter.GetBytes(_rng.Next(int.MinValue, int.MaxValue)),
|
||||
0
|
||||
);
|
||||
try
|
||||
{
|
||||
object sort = bindingContext.ModelType.GetMethod(nameof(Sort<object>.From))!
|
||||
.Invoke(null, new object?[] { sortBy.FirstValue, seed })!;
|
||||
bindingContext.Result = ModelBindingResult.Success(sort);
|
||||
bindingContext.HttpContext.Items["seed"] = seed;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (TargetInvocationException ex)
|
||||
{
|
||||
throw ex.InnerException!;
|
||||
}
|
||||
}
|
||||
|
||||
public class Provider : IModelBinderProvider
|
||||
{
|
||||
public IModelBinder GetBinder(ModelBinderProviderContext context)
|
||||
{
|
||||
if (context.Metadata.ModelType.Name == "Sort`1")
|
||||
{
|
||||
return new BinderTypeModelBinder(typeof(SortBinder));
|
||||
}
|
||||
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
@ -81,16 +81,15 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<PeopleRole>>> GetRoles(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<PeopleRole> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
Expression<Func<PeopleRole, bool>>? whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
|
||||
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
|
||||
|
||||
ICollection<PeopleRole> resources = await identifier.Match(
|
||||
id => _libraryManager.GetRolesFromPeople(id, whereQuery, sort, pagination),
|
||||
slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sort, pagination)
|
||||
id => _libraryManager.GetRolesFromPeople(id, whereQuery, sortBy, pagination),
|
||||
slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sortBy, pagination)
|
||||
);
|
||||
|
||||
return Page(resources, pagination.Limit);
|
||||
|
@ -78,13 +78,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Show> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)),
|
||||
Sort<Show>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
@ -79,13 +79,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Show> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Show> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections!)),
|
||||
Sort<Show>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
@ -131,13 +131,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Collection> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies!)),
|
||||
Sort<Collection>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
@ -79,13 +79,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Episode>>> GetEpisode(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Episode> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)),
|
||||
Sort<Episode>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
@ -81,13 +81,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Season>>> GetSeasons(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Season> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Season> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)),
|
||||
Sort<Season>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
@ -116,13 +116,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Episode>>> GetEpisodes(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Episode> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Episode> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)),
|
||||
Sort<Episode>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
@ -151,16 +151,15 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<PeopleRole>>> GetPeople(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<PeopleRole> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
Expression<Func<PeopleRole, bool>>? whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
|
||||
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
|
||||
|
||||
ICollection<PeopleRole> resources = await identifier.Match(
|
||||
id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination),
|
||||
slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination)
|
||||
id => _libraryManager.GetPeopleFromShow(id, whereQuery, sortBy, pagination),
|
||||
slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sortBy, pagination)
|
||||
);
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
@ -203,13 +202,13 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Sort<Collection> sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Collection> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
|
||||
Sort<Collection>.From(sortBy),
|
||||
sortBy,
|
||||
pagination
|
||||
);
|
||||
|
||||
|
@ -23,7 +23,6 @@ using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using EFCore.NamingConventions.Internal;
|
||||
|
Loading…
x
Reference in New Issue
Block a user