mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add custom relations on library items (first pass)
This commit is contained in:
parent
eed058c891
commit
ba83edd26c
@ -2,6 +2,7 @@
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.MaintainabilityRules">
|
||||
<Rule Id="SA1413" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||
<Rule Id="SA1414" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||
<Rule Id="SA1114" Action="None" /> <!-- UseTrailingCommasInMultiLineInitializers -->
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.OrderingRules">
|
||||
<Rule Id="SA1201" Action="None" /> <!-- ElementsMustAppearInTheCorrectOrder -->
|
||||
|
@ -31,6 +31,10 @@ namespace Kyoo.Abstractions.Models.Attributes
|
||||
/// </summary>
|
||||
public string? RelationID { get; }
|
||||
|
||||
public string? Sql { get; set; }
|
||||
|
||||
public string? On { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LoadableRelationAttribute"/>.
|
||||
/// </summary>
|
||||
|
@ -152,7 +152,23 @@ namespace Kyoo.Abstractions.Models
|
||||
/// The first episode of this show.
|
||||
/// </summary>
|
||||
[Projectable(UseMemberBody = nameof(_FirstEpisode), OnlyOnInclude = true)]
|
||||
[LoadableRelation] public Episode? FirstEpisode { get; set; }
|
||||
[LoadableRelation(
|
||||
// language=PostgreSQL
|
||||
Sql = """
|
||||
select
|
||||
fe.*
|
||||
from (
|
||||
select
|
||||
e.*,
|
||||
row_number() over (partition by e.show_id order by e.absolute_number, e.season_number, e.episode_number) as number
|
||||
from
|
||||
episodes as e) as fe
|
||||
where
|
||||
fe.number <= 1
|
||||
""",
|
||||
On = "show_id"
|
||||
)]
|
||||
public Episode? FirstEpisode { get; set; }
|
||||
|
||||
private Episode? _FirstEpisode => Episodes!
|
||||
.OrderBy(x => x.AbsoluteNumber)
|
||||
|
@ -17,7 +17,6 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@ -47,32 +46,42 @@ public class Include<T>
|
||||
if (string.IsNullOrEmpty(fields))
|
||||
return new Include<T>();
|
||||
|
||||
Type[] types = typeof(T).GetCustomAttribute<OneOfAttribute>()?.Types ?? new[] { typeof(T) };
|
||||
return new Include<T>
|
||||
{
|
||||
Metadatas = fields.Split(',').Select<string, Metadata>(key =>
|
||||
Metadatas = fields.Split(',').SelectMany(key =>
|
||||
{
|
||||
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();
|
||||
LoadableRelationAttribute? attr = prop?.GetCustomAttribute<LoadableRelationAttribute>();
|
||||
if (prop == null || attr == null)
|
||||
var relations = types
|
||||
.Select(x => x.GetProperty(key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)!)
|
||||
.Select(prop => (prop, attr: prop?.GetCustomAttribute<LoadableRelationAttribute>()!))
|
||||
.Where(x => x.prop != null && x.attr != null)
|
||||
.ToList();
|
||||
if (!relations.Any())
|
||||
throw new ValidationException($"No loadable relation with the name {key}.");
|
||||
if (attr.RelationID != null)
|
||||
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID);
|
||||
return relations
|
||||
.Select(x =>
|
||||
{
|
||||
(PropertyInfo prop, LoadableRelationAttribute attr) = x;
|
||||
|
||||
// Multiples relations are disabled due to:
|
||||
// - Cartesian Explosions perfs
|
||||
// - Code complexity added.
|
||||
// if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
|
||||
// {
|
||||
// // The property is either a list or a an array.
|
||||
// return new MultipleRelation(
|
||||
// prop.Name,
|
||||
// prop.PropertyType.GetElementType() ?? prop.PropertyType.GenericTypeArguments.First()
|
||||
// );
|
||||
// }
|
||||
throw new NotImplementedException();
|
||||
if (attr.RelationID != null)
|
||||
return new SingleRelation(prop.Name, prop.PropertyType, attr.RelationID) as Metadata;
|
||||
|
||||
// Multiples relations are disabled due to:
|
||||
// - Cartesian Explosions perfs
|
||||
// - Code complexity added.
|
||||
// if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && prop.PropertyType != typeof(string))
|
||||
// {
|
||||
// // The property is either a list or a an array.
|
||||
// return new MultipleRelation(
|
||||
// prop.Name,
|
||||
// prop.PropertyType.GetElementType() ?? prop.PropertyType.GenericTypeArguments.First()
|
||||
// );
|
||||
// }
|
||||
if (attr.Sql != null && attr.On != null)
|
||||
return new CustomRelation(prop.Name, prop.PropertyType, attr.Sql, attr.On, prop.DeclaringType!);
|
||||
throw new NotImplementedException();
|
||||
})
|
||||
.Distinct();
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
@ -80,4 +89,6 @@ public class Include<T>
|
||||
public abstract record Metadata(string Name);
|
||||
|
||||
public record SingleRelation(string Name, Type type, string RelationIdName) : Metadata(Name);
|
||||
|
||||
public record CustomRelation(string Name, Type type, string Sql, string On, Type Declaring) : Metadata(Name);
|
||||
}
|
||||
|
@ -131,25 +131,36 @@ namespace Kyoo.Core.Controllers
|
||||
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
||||
where T : class
|
||||
{
|
||||
int relation = 0;
|
||||
Dictionary<string, Type> retConfig = new();
|
||||
StringBuilder join = new();
|
||||
|
||||
foreach (Include<T>.Metadata metadata in include.Metadatas)
|
||||
{
|
||||
relation++;
|
||||
switch (metadata)
|
||||
{
|
||||
case Include<T>.SingleRelation(var name, var type, var rid):
|
||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||
retConfig.Add(tableName, type);
|
||||
join.AppendLine($"left join {tableName} on {tableName}.id = {_Property(rid, config)}");
|
||||
retConfig.Add($"r{relation}", type);
|
||||
join.AppendLine($"left join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
||||
break;
|
||||
case Include<T>.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||
string owner = config.First(x => x.Value == declaring).Key;
|
||||
retConfig.Add($"r{relation}", type);
|
||||
join.AppendLine($"left join ({sql}) as r{relation} on r{relation}.{on} = {owner}.id");
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
T Map(T item, IEnumerable<object> relations)
|
||||
{
|
||||
foreach ((string name, object value) in include.Fields.Zip(relations))
|
||||
foreach ((string name, object? value) in include.Fields.Zip(relations))
|
||||
{
|
||||
if (value == null)
|
||||
continue;
|
||||
PropertyInfo? prop = item.GetType().GetProperty(name);
|
||||
if (prop != null)
|
||||
prop.SetValue(item, value);
|
||||
|
Loading…
x
Reference in New Issue
Block a user