mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Rework default sort and make it work with dapper
This commit is contained in:
parent
9ea177e2f6
commit
177391a74c
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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();
|
||||
}
|
@ -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; }
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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()
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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 />
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()) },
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
{ }
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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))!)
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user