Rework default sort and make it work with dapper

This commit is contained in:
Zoe Roux 2023-11-19 21:18:44 +01:00
parent 9ea177e2f6
commit 177391a74c
40 changed files with 226 additions and 131 deletions

View File

@ -26,7 +26,7 @@ namespace Kyoo.Abstractions.Controllers
public interface ILibraryManager
{
IRepository<T> Repository<T>()
where T : class, IResource;
where T : class, IResource, IQuery;
/// <summary>
/// The repository that handle libraries items (a wrapper around shows and collections).

View File

@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Controllers
/// </summary>
/// <typeparam name="T">The resource's type that this repository manage.</typeparam>
public interface IRepository<T> : IBaseRepository
where T : class, IResource
where T : class, IResource, IQuery
{
/// <summary>
/// The event handler type for all events of this repository.

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models;
@ -24,4 +25,7 @@ namespace Kyoo.Abstractions.Models;
/// A show, a movie or a collection.
/// </summary>
[OneOf(Types = new[] { typeof(Show), typeof(Movie), typeof(Collection) })]
public interface ILibraryItem : IResource, IThumbnails, IMetadata, IAddedDate { }
public interface ILibraryItem : IResource, IThumbnails, IMetadata, IAddedDate, IQuery
{
static Sort IQuery.DefaultSort => new Sort<ILibraryItem>.By(nameof(Movie.AirDate));
}

View File

@ -41,7 +41,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A new item
/// </summary>
public class News : IResource, IMetadata, IThumbnails, IAddedDate
public class News : IResource, IMetadata, IThumbnails, IAddedDate, IQuery
{
/// <inheritdoc />
public int Id { get; set; }

View File

@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -28,8 +29,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A class representing collections of <see cref="Show"/>.
/// </summary>
public class Collection : IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem
public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem
{
public static Sort DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <inheritdoc />
public int Id { get; set; }

View File

@ -23,6 +23,7 @@ using System.Linq;
using System.Text.RegularExpressions;
using EntityFrameworkCore.Projectables;
using JetBrains.Annotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
@ -30,8 +31,15 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A class to represent a single show's episode.
/// </summary>
public class Episode : IResource, IMetadata, IThumbnails, IAddedDate
public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
{
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
public static Sort DefaultSort => new Sort<Episode>.Conglomerate(
new Sort<Episode>.By(x => x.AbsoluteNumber),
new Sort<Episode>.By(x => x.SeasonNumber),
new Sort<Episode>.By(x => x.EpisodeNumber)
);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -0,0 +1,30 @@
// 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 Kyoo.Abstractions.Controllers;
namespace Kyoo.Abstractions.Models;
public interface IQuery
{
/// <summary>
/// The sorting that will be used when no user defined one is present.
/// </summary>
public static virtual Sort DefaultSort => throw new NotImplementedException();
}

View File

@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -28,8 +29,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A series or a movie.
/// </summary>
public class Movie : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
{
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -27,8 +28,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
/// </summary>
public class People : IResource, IMetadata, IThumbnails
public class People : IQuery, IResource, IMetadata, IThumbnails
{
public static Sort DefaultSort => new Sort<People>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -23,6 +23,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Text.RegularExpressions;
using EntityFrameworkCore.Projectables;
using JetBrains.Annotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
namespace Kyoo.Abstractions.Models
@ -30,8 +31,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A season of a <see cref="Show"/>.
/// </summary>
public class Season : IResource, IMetadata, IThumbnails, IAddedDate
public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
{
public static Sort DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -30,8 +31,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A series or a movie.
/// </summary>
public class Show : IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
public class Show : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
{
public static Sort DefaultSort => new Sort<Show>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }
@ -107,7 +110,8 @@ namespace Kyoo.Abstractions.Models
/// </summary>
public string? Trailer { get; set; }
[SerializeIgnore] public DateTime? AirDate => StartAir;
[SerializeIgnore]
public DateTime? AirDate => StartAir;
/// <inheritdoc />
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();

View File

@ -18,6 +18,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -27,8 +28,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A studio that make shows.
/// </summary>
public class Studio : IResource, IMetadata
public class Studio : IQuery, IResource, IMetadata
{
public static Sort DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
@ -28,8 +29,10 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A single user of the app.
/// </summary>
public class User : IResource, IAddedDate
public class User : IQuery, IResource, IAddedDate
{
public static Sort DefaultSort => new Sort<User>.By(x => x.Username);
/// <inheritdoc />
public int Id { get; set; }

View File

@ -43,11 +43,14 @@ public class Include<T>
return new Include<T>
{
Fields = fields.Split(',').Select(x =>
Fields = fields.Split(',').Select(key =>
{
PropertyInfo? prop = typeof(T).GetProperty(x, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
PropertyInfo? prop = types
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance))
.FirstOrDefault();
if (prop?.GetCustomAttribute<LoadableRelationAttribute>() == null)
throw new ValidationException($"No loadable relation with the name {x}.");
throw new ValidationException($"No loadable relation with the name {key}.");
return prop.Name;
}).ToArray()
};

View File

@ -21,15 +21,20 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Controllers
{
public record Sort;
/// <summary>
/// Information about how a query should be sorted. What factor should decide the sort and in which order.
/// </summary>
/// <typeparam name="T">For witch type this sort applies</typeparam>
public record Sort<T>
public record Sort<T> : Sort
where T : IQuery
{
/// <summary>
/// Sort by a specific key
@ -61,7 +66,13 @@ namespace Kyoo.Abstractions.Controllers
public record Random(uint seed) : Sort<T>;
/// <summary>The default sort method for the given type.</summary>
public record Default : Sort<T>;
public record Default : Sort<T>
{
public void Deconstruct(out Sort<T> value)
{
value = (Sort<T>)T.DefaultSort;
}
}
/// <summary>
/// Create a new <see cref="Sort{T}"/> instance from a key's name (case insensitive).
@ -91,7 +102,11 @@ namespace Kyoo.Abstractions.Controllers
null => false,
_ => 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);
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
PropertyInfo? property = types
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance))
.FirstOrDefault();
if (property == null)
throw new ValidationException("The given sort key is not valid.");
return new By(property.Name, desendant);

View File

@ -34,6 +34,69 @@ namespace Kyoo.Utils
/// </summary>
public static class Utility
{
/// <summary>
/// Convert a string to snake case. Stollen from
/// https://github.com/efcore/EFCore.NamingConventions/blob/main/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs
/// </summary>
/// <param name="name">The string to convert.</param>
/// <returns>The string in snake case</returns>
public static string ToSnakeCase(this string name)
{
StringBuilder builder = new(name.Length + Math.Min(2, name.Length / 5));
UnicodeCategory? previousCategory = default;
for (int currentIndex = 0; currentIndex < name.Length; currentIndex++)
{
char currentChar = name[currentIndex];
if (currentChar == '_')
{
builder.Append('_');
previousCategory = null;
continue;
}
UnicodeCategory currentCategory = char.GetUnicodeCategory(currentChar);
switch (currentCategory)
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.TitlecaseLetter:
if (previousCategory == UnicodeCategory.SpaceSeparator ||
previousCategory == UnicodeCategory.LowercaseLetter ||
(previousCategory != UnicodeCategory.DecimalDigitNumber &&
previousCategory != null &&
currentIndex > 0 &&
currentIndex + 1 < name.Length &&
char.IsLower(name[currentIndex + 1])))
{
builder.Append('_');
}
currentChar = char.ToLowerInvariant(currentChar);
break;
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.DecimalDigitNumber:
if (previousCategory == UnicodeCategory.SpaceSeparator)
{
builder.Append('_');
}
break;
default:
if (previousCategory != null)
{
previousCategory = UnicodeCategory.SpaceSeparator;
}
continue;
}
builder.Append(currentChar);
previousCategory = currentCategory;
}
return builder.ToString();
}
/// <summary>
/// Is the lambda expression a member (like x => x.Body).
/// </summary>

View File

@ -92,7 +92,7 @@ namespace Kyoo.Core.Controllers
public IRepository<User> Users { get; }
public IRepository<T> Repository<T>()
where T : class, IResource
where T : class, IResource, IQuery
{
return (IRepository<T>)_repositories.First(x => x.RepositoryType == typeof(T));
}

View File

@ -19,7 +19,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
@ -39,9 +38,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Sort<Collection> DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <summary>
/// Create a new <see cref="CollectionRepository"/>.
/// </summary>
@ -56,11 +52,10 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Collection>> Search(string query, Include<Collection>? include = default)
{
return await Sort(
AddIncludes(_database.Collections, include)
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
).ToListAsync();
return await AddIncludes(_database.Collections, include)
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}
/// <inheritdoc />

View File

@ -41,14 +41,6 @@ namespace Kyoo.Core.Controllers
private readonly IRepository<Show> _shows;
/// <inheritdoc />
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
protected override Sort<Episode> DefaultSort => new Sort<Episode>.Conglomerate(
new Sort<Episode>.By(x => x.AbsoluteNumber),
new Sort<Episode>.By(x => x.SeasonNumber),
new Sort<Episode>.By(x => x.EpisodeNumber)
);
static EpisodeRepository()
{
// Edit episode slugs when the show's slug changes.
@ -86,10 +78,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Episode>> Search(string query, Include<Episode>? include = default)
{
return await Sort(
AddIncludes(_database.Episodes, include)
.Where(_database.Like<Episode>(x => x.Name!, $"%{query}%"))
)
return await AddIncludes(_database.Episodes, include)
.Where(_database.Like<Episode>(x => x.Name!, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -30,6 +30,7 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Utils;
namespace Kyoo.Core.Controllers
{
@ -91,17 +92,30 @@ namespace Kyoo.Core.Controllers
throw new NotImplementedException();
}
public string ProcessSort<T>(Sort<T> sort, string[] tables)
public string ProcessSort<T>(Sort<T> sort, Dictionary<string, Type> config)
where T : IQuery
{
return sort switch
string Property(string key)
{
// TODO: Implement default sort by
Sort<T>.Default => $"coalesce({string.Join(", ", tables.Select(x => $"{x}.name"))})",
Sort<T>.By(string key, bool desc) => $"coalesce({string.Join(", ", tables.Select(x => $"{x}.{key}"))}) {(desc ? "desc" : "asc")}",
Sort<T>.Random(var seed) => $"md5('{seed}' || coalesce({string.Join(", ", tables.Select(x => $"{x}.id"))}))",
Sort<T>.Conglomerate(var list) => string.Join(", ", list.Select(x => ProcessSort(x, tables))),
if (config.Count == 1)
return $"{config.First()}.{key.ToSnakeCase()}";
IEnumerable<string> keys = config
.Where(x => x.Value.GetProperty(key) != null)
.Select(x => $"{x.Key}.{key.ToSnakeCase()}");
return $"coalesce({string.Join(", ", keys)})";
}
string ret = sort switch
{
Sort<T>.Default(var value) => ProcessSort(value, config),
Sort<T>.By(string key, bool desc) => $"{Property(key)} {(desc ? "desc nulls last" : "asc")}",
Sort<T>.Random(var seed) => $"md5('{seed}' || {Property("id")})",
Sort<T>.Conglomerate(var list) => string.Join(", ", list.Select(x => ProcessSort(x, config))),
_ => throw new SwitchExpressionException(),
};
// always end query by an id sort.
return $"{ret}, {Property("id")} asc";
}
public async Task<ICollection<ILibraryItem>> GetAll(
@ -110,6 +124,12 @@ namespace Kyoo.Core.Controllers
Pagination? limit = null,
Include<ILibraryItem>? include = null)
{
Dictionary<string, Type> config = new()
{
{ "s", typeof(Show) },
{ "m", typeof(Movie) },
{ "c", typeof(Collection) }
};
// language=PostgreSQL
IDapperSqlCommand query = _database.SqlBuilder($"""
select
@ -130,11 +150,11 @@ namespace Kyoo.Core.Controllers
from
collections) as c on false
left join studios as st on st.id = coalesce(s.studio_id, m.studio_id)
order by {ProcessSort(sort, new[] { "s", "m", "c" }):raw}
order by {ProcessSort(sort, config):raw}
limit {limit.Limit}
""").Build();
Type[] types = new[] { typeof(Show), typeof(Movie), typeof(Collection), typeof(Studio) };
Type[] types = config.Select(x => x.Value).Concat(new[] { typeof(Studio) }).ToArray();
IEnumerable<ILibraryItem> data = await query.QueryAsync<ILibraryItem>(types, items =>
{
var studio = items[3] as Studio;

View File

@ -40,7 +40,7 @@ namespace Kyoo.Core.Controllers
/// </summary>
/// <typeparam name="T">The type of this repository</typeparam>
public abstract class LocalRepository<T> : IRepository<T>
where T : class, IResource
where T : class, IResource, IQuery
{
/// <summary>
/// The Entity Framework's Database handle.
@ -52,11 +52,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly IThumbnailsManager _thumbs;
/// <summary>
/// The default sort order that will be used for this resource's type.
/// </summary>
protected abstract Sort<T> DefaultSort { get; }
/// <summary>
/// Create a new base <see cref="LocalRepository{T}"/> with the given database handle.
/// </summary>
@ -77,9 +72,9 @@ namespace Kyoo.Core.Controllers
/// <param name="query">The query to sort.</param>
/// <param name="sortBy">How to sort the query.</param>
/// <returns>The newly sorted query.</returns>
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T>? sortBy = null)
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T>? sortBy)
{
sortBy ??= DefaultSort;
sortBy ??= new Sort<T>.Default();
IOrderedQueryable<T> _SortBy(IQueryable<T> qr, Expression<Func<T, object>> sort, bool desc, bool then)
{
@ -98,8 +93,8 @@ namespace Kyoo.Core.Controllers
{
switch (sortBy)
{
case Sort<T>.Default:
return _Sort(query, DefaultSort, then);
case Sort<T>.Default(var value):
return _Sort(query, value, then);
case Sort<T>.By(var key, var desc):
return _SortBy(query, x => EF.Property<T>(x, key), desc, then);
case Sort<T>.Random(var seed):
@ -154,7 +149,7 @@ namespace Kyoo.Core.Controllers
T reference,
bool next = true)
{
sort ??= DefaultSort;
sort ??= new Sort<T>.Default();
// x =>
ParameterExpression x = Expression.Parameter(typeof(T), "x");
@ -173,7 +168,7 @@ namespace Kyoo.Core.Controllers
{
return sort switch
{
Sort<T>.Default => GetSortsBy(DefaultSort),
Sort<T>.Default(var value) => GetSortsBy(value),
Sort<T>.By @sortBy => new[] { new SortIndicator(sortBy.Key, sortBy.Desendant, null) },
Sort<T>.Conglomerate(var list) => list.SelectMany(GetSortsBy),
Sort<T>.Random(var seed) => new[] { new SortIndicator("random", false, seed.ToString()) },

View File

@ -47,9 +47,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly IRepository<People> _people;
/// <inheritdoc />
protected override Sort<Movie> DefaultSort => new Sort<Movie>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="MovieRepository"/>.
/// </summary>
@ -71,10 +68,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Movie>> Search(string query, Include<Movie>? include = default)
{
return await Sort(
AddIncludes(_database.Movies, include)
.Where(_database.Like<Movie>(x => x.Name + " " + x.Slug, $"%{query}%"))
)
return await AddIncludes(_database.Movies, include)
.Where(_database.Like<Movie>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -31,9 +31,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
public class NewsRepository : LocalRepository<News>
{
/// <inheritdoc />
protected override Sort<News> DefaultSort => new Sort<News>.By(x => x.AddedDate, true);
public NewsRepository(DatabaseContext database, IThumbnailsManager thumbs)
: base(database, thumbs)
{ }

View File

@ -44,9 +44,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly Lazy<IRepository<Show>> _shows;
/// <inheritdoc />
protected override Sort<People> DefaultSort => new Sort<People>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="PeopleRepository"/>
/// </summary>
@ -65,10 +62,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<People>> Search(string query, Include<People>? include = default)
{
return await Sort(
AddIncludes(_database.People, include)
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
)
return await AddIncludes(_database.People, include)
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -39,9 +39,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc/>
protected override Sort<Season> DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
static SeasonRepository()
{
// Edit seasons slugs when the show's slug changes.
@ -76,10 +73,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public override async Task<ICollection<Season>> Search(string query, Include<Season>? include = default)
{
return await Sort(
AddIncludes(_database.Seasons, include)
.Where(_database.Like<Season>(x => x.Name!, $"%{query}%"))
)
return await AddIncludes(_database.Seasons, include)
.Where(_database.Like<Season>(x => x.Name!, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -48,9 +48,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly IRepository<People> _people;
/// <inheritdoc />
protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="ShowRepository"/>.
/// </summary>
@ -72,10 +69,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Show>> Search(string query, Include<Show>? include = default)
{
return await Sort(
AddIncludes(_database.Shows, include)
.Where(_database.Like<Show>(x => x.Name + " " + x.Slug, $"%{query}%"))
)
return await AddIncludes(_database.Shows, include)
.Where(_database.Like<Show>(x => x.Name + " " + x.Slug, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -38,9 +38,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Sort<Studio> DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="StudioRepository"/>.
/// </summary>
@ -55,10 +52,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<Studio>> Search(string query, Include<Studio>? include = default)
{
return await Sort(
AddIncludes(_database.Studios, include)
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
)
return await AddIncludes(_database.Studios, include)
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -37,9 +37,6 @@ namespace Kyoo.Core.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Sort<User> DefaultSort => new Sort<User>.By(x => x.Username);
/// <summary>
/// Create a new <see cref="UserRepository"/>
/// </summary>
@ -54,10 +51,8 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public override async Task<ICollection<User>> Search(string query, Include<User>? include = default)
{
return await Sort(
AddIncludes(_database.Users, include)
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
)
return await AddIncludes(_database.Users, include)
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
.Take(20)
.ToListAsync();
}

View File

@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
[ApiController]
[ResourceView]
public class CrudApi<T> : BaseApi
where T : class, IResource
where T : class, IResource, IQuery
{
/// <summary>
/// The repository of the resource, used to retrieve, save and do operations on the baking store.

View File

@ -36,7 +36,7 @@ namespace Kyoo.Core.Api
[ApiController]
[ResourceView]
public class CrudThumbsApi<T> : CrudApi<T>
where T : class, IResource, IThumbnails
where T : class, IResource, IThumbnails, IQuery
{
/// <summary>
/// The thumbnail manager used to retrieve images paths.

View File

@ -77,7 +77,7 @@ namespace Kyoo.Core.Api
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
if (properties.All(x => x.PropertyName != "kind"))
if (properties.All(x => x.PropertyName != "kind") && type.IsAssignableTo(typeof(IResource)))
{
properties.Add(new JsonProperty()
{

View File

@ -20,6 +20,7 @@ using System;
using System.Reflection;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
@ -38,7 +39,7 @@ public class SortBinder : IModelBinder
);
try
{
object sort = bindingContext.ModelType.GetMethod(nameof(Sort<object>.From))!
object sort = bindingContext.ModelType.GetMethod(nameof(Sort<Movie>.From))!
.Invoke(null, new object?[] { sortBy.FirstValue, seed })!;
bindingContext.Result = ModelBindingResult.Success(sort);
bindingContext.HttpContext.Items["seed"] = seed;

View File

@ -31,6 +31,7 @@ public class SearchManager : ISearchManager
private readonly ILibraryManager _libraryManager;
private static IEnumerable<string> _GetSortsBy<T>(string index, Sort<T>? sort)
where T : IQuery
{
return sort switch
{
@ -55,7 +56,7 @@ public class SearchManager : ISearchManager
Sort<T>? sortBy = default,
SearchPagination? pagination = default,
Include<T>? include = default)
where T : class, IResource
where T : class, IResource, IQuery
{
// TODO: add filters and facets
ISearchable<IdResource> res = await _client.Index(index).SearchAsync<IdResource>(query, new SearchQuery()

View File

@ -193,10 +193,6 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<ItemKind>("Kind")
.HasColumnType("item_kind")
.HasColumnName("kind");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")

View File

@ -194,10 +194,6 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<ItemKind>("Kind")
.HasColumnType("item_kind")
.HasColumnName("kind");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")

View File

@ -198,10 +198,6 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<ItemKind>("Kind")
.HasColumnType("item_kind")
.HasColumnName("kind");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")

View File

@ -195,10 +195,6 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<ItemKind>("Kind")
.HasColumnType("item_kind")
.HasColumnName("kind");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")

View File

@ -50,7 +50,6 @@ namespace Kyoo.Postgresql
{
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemKind>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<NewsKind>();
}
@ -104,7 +103,6 @@ namespace Kyoo.Postgresql
{
modelBuilder.HasPostgresEnum<Status>();
modelBuilder.HasPostgresEnum<Genre>();
modelBuilder.HasPostgresEnum<ItemKind>();
modelBuilder.HasPostgresEnum<NewsKind>();
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)

View File

@ -21,7 +21,6 @@ using System.Collections.Generic;
using System.Data.Common;
using System.Text.RegularExpressions;
using Dapper;
using EFCore.NamingConventions.Internal;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Postgresql.Utils;