Adding a field property with auto loading & removing non-loaded fields from the json serialization. Reworking all json serialization

This commit is contained in:
Zoe Roux 2021-02-27 23:34:28 +01:00
parent 97ab1affa0
commit faf453e64a
30 changed files with 191 additions and 210 deletions

View File

@ -69,10 +69,10 @@ namespace Kyoo.Controllers
where T : class, IResource
where T2 : class, new();
Task<T> Load<T>(T obj, string memberName)
Task<T> Load<T>([NotNull] T obj, string memberName)
where T : class, IResource;
Task Load(IResource obj, string memberName);
Task Load([NotNull] IResource obj, string memberName);
// Library Items relations
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,

View File

@ -3,5 +3,5 @@ using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Class)]
public class ComposedSlug : Attribute { }
public class ComposedSlugAttribute : Attribute { }
}

View File

@ -1,7 +0,0 @@
using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class EditableRelation : Attribute { }
}

View File

@ -8,7 +8,4 @@ namespace Kyoo.Models.Attributes
{
void OnMerge(object merged);
}
public class JsonReadOnly : Attribute { }
public class JsonIgnore : JsonReadOnly { }
}

View File

@ -0,0 +1,10 @@
using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class EditableRelationAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property)]
public class LoadableRelationAttribute : Attribute { }
}

View File

@ -0,0 +1,10 @@
using System;
namespace Kyoo.Models.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeIgnoreAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DeserializeIgnoreAttribute : Attribute {}
}

View File

@ -4,21 +4,21 @@ namespace Kyoo.Models
{
public class MetadataID
{
[JsonIgnore] public int ID { get; set; }
[JsonIgnore] public int ProviderID { get; set; }
[SerializeIgnore] public int ID { get; set; }
[SerializeIgnore] public int ProviderID { get; set; }
public virtual ProviderID Provider {get; set; }
[JsonIgnore] public int? ShowID { get; set; }
[JsonIgnore] public virtual Show Show { get; set; }
[SerializeIgnore] public int? ShowID { get; set; }
[SerializeIgnore] public virtual Show Show { get; set; }
[JsonIgnore] public int? EpisodeID { get; set; }
[JsonIgnore] public virtual Episode Episode { get; set; }
[SerializeIgnore] public int? EpisodeID { get; set; }
[SerializeIgnore] public virtual Episode Episode { get; set; }
[JsonIgnore] public int? SeasonID { get; set; }
[JsonIgnore] public virtual Season Season { get; set; }
[SerializeIgnore] public int? SeasonID { get; set; }
[SerializeIgnore] public virtual Season Season { get; set; }
[JsonIgnore] public int? PeopleID { get; set; }
[JsonIgnore] public virtual People People { get; set; }
[SerializeIgnore] public int? PeopleID { get; set; }
[SerializeIgnore] public virtual People People { get; set; }
public string DataID { get; set; }
public string Link { get; set; }

View File

@ -8,9 +8,9 @@ namespace Kyoo.Models
{
public class PeopleRole : IResource
{
[JsonIgnore] public int ID { get; set; }
[JsonIgnore] public int PeopleID { get; set; }
[JsonIgnore] public virtual People People { get; set; }
[SerializeIgnore] public int ID { get; set; }
[SerializeIgnore] public int PeopleID { get; set; }
[SerializeIgnore] public virtual People People { get; set; }
[ExpressionRewrite(nameof(People) + "." + nameof(Models.People.Slug))]
public string Slug
@ -40,8 +40,8 @@ namespace Kyoo.Models
set => People.ExternalIDs = value;
}
[JsonIgnore] public int ShowID { get; set; }
[JsonIgnore] public virtual Show Show { get; set; }
[SerializeIgnore] public int ShowID { get; set; }
[SerializeIgnore] public virtual Show Show { get; set; }
public string Role { get; set; }
public string Type { get; set; }
@ -77,7 +77,7 @@ namespace Kyoo.Models
public string Slug { get; set; }
public string Title { get; set; }
public ICollection<string> Aliases { get; set; }
[JsonIgnore] public string Path { get; set; }
[SerializeIgnore] public string Path { get; set; }
public string Overview { get; set; }
public Status? Status { get; set; }
public string TrailerUrl { get; set; }

View File

@ -10,8 +10,8 @@ namespace Kyoo.Models
public string Name { get; set; }
public string Poster { get; set; }
public string Overview { get; set; }
[JsonIgnore] public virtual ICollection<Show> Shows { get; set; }
[JsonIgnore] public virtual ICollection<Library> Libraries { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
[LoadableRelation] public virtual ICollection<Library> Libraries { get; set; }
public Collection() { }

View File

@ -9,37 +9,29 @@ namespace Kyoo.Models
public class Episode : IResource, IOnMerge
{
public int ID { get; set; }
public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString();
public int ShowID { get; set; }
[JsonIgnore] public virtual Show Show { get; set; }
[LoadableRelation] public virtual Show Show { get; set; }
public int? SeasonID { get; set; }
[JsonIgnore] public virtual Season Season { get; set; }
[LoadableRelation] public virtual Season Season { get; set; }
public int SeasonNumber { get; set; } = -1;
public int EpisodeNumber { get; set; } = -1;
public int AbsoluteNumber { get; set; } = -1;
[JsonIgnore] public string Path { get; set; }
[SerializeIgnore] public string Path { get; set; }
public string Thumb => $"/api/episodes/{Slug}/thumb";
public string Title { get; set; }
public string Overview { get; set; }
public DateTime? ReleaseDate { get; set; }
public int Runtime { get; set; } //This runtime variable should be in minutes
[JsonIgnore] public string Poster { get; set; }
[EditableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[SerializeIgnore] public string Poster { get; set; }
[LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual ICollection<Track> Tracks { get; set; }
[LoadableRelation] public virtual ICollection<Track> Tracks { get; set; }
public string ShowTitle => Show.Title;
public string Slug => Show != null ? GetSlug(Show.Slug, SeasonNumber, EpisodeNumber) : ID.ToString();
public string Thumb
{
get
{
if (Show != null)
return "thumb/" + Slug;
return Poster;
}
}
public string ShowTitle => Show?.Title;
public Episode() { }

View File

@ -5,11 +5,11 @@ namespace Kyoo.Models
{
public class Genre : IResource
{
[JsonIgnore] public int ID { get; set; }
public int ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
[JsonIgnore] public virtual ICollection<Show> Shows { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
public Genre() {}

View File

@ -6,15 +6,15 @@ namespace Kyoo.Models
{
public class Library : IResource
{
[JsonIgnore] public int ID { get; set; }
public int ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
public string[] Paths { get; set; }
[EditableRelation] public virtual ICollection<ProviderID> Providers { get; set; }
[JsonIgnore] public virtual ICollection<Show> Shows { get; set; }
[JsonIgnore] public virtual ICollection<Collection> Collections { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
[LoadableRelation] public virtual ICollection<Collection> Collections { get; set; }
public Library() { }

View File

@ -10,9 +10,9 @@ namespace Kyoo.Models
public string Slug { get; set; }
public string Name { get; set; }
public string Poster { get; set; }
[EditableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [JsonReadOnly] public virtual ICollection<PeopleRole> Roles { get; set; }
[EditableRelation] [LoadableRelation] public virtual ICollection<PeopleRole> Roles { get; set; }
public People() {}

View File

@ -4,7 +4,7 @@ namespace Kyoo.Models
{
public class ProviderID : IResource
{
[JsonIgnore] public int ID { get; set; }
public int ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
public string Logo { get; set; }

View File

@ -7,8 +7,8 @@ namespace Kyoo.Models
[ComposedSlug]
public class Season : IResource
{
[JsonIgnore] public int ID { get; set; }
[JsonIgnore] public int ShowID { get; set; }
public int ID { get; set; }
public int ShowID { get; set; }
public int SeasonNumber { get; set; } = -1;
@ -17,11 +17,12 @@ namespace Kyoo.Models
public string Overview { get; set; }
public int? Year { get; set; }
[JsonIgnore] public string Poster { get; set; }
[EditableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[SerializeIgnore] public string Poster { get; set; }
public string Thumb => $"/api/seasons/{Slug}/thumb";
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual Show Show { get; set; }
[JsonIgnore] public virtual ICollection<Episode> Episodes { get; set; }
[LoadableRelation] public virtual Show Show { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }
public Season() { }

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
public string Slug { get; set; }
public string Title { get; set; }
[EditableRelation] public string[] Aliases { get; set; }
[JsonIgnore] public string Path { get; set; }
[SerializeIgnore] public string Path { get; set; }
public string Overview { get; set; }
public Status? Status { get; set; }
public string TrailerUrl { get; set; }
@ -24,17 +24,17 @@ namespace Kyoo.Models
public bool IsMovie { get; set; }
[EditableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public int? StudioID { get; set; }
[EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; }
[EditableRelation] [JsonReadOnly] public virtual ICollection<Genre> Genres { get; set; }
[EditableRelation] [JsonReadOnly] public virtual ICollection<PeopleRole> People { get; set; }
[JsonIgnore] public virtual ICollection<Season> Seasons { get; set; }
[JsonIgnore] public virtual ICollection<Episode> Episodes { get; set; }
[JsonIgnore] public virtual ICollection<Library> Libraries { get; set; }
[JsonIgnore] public virtual ICollection<Collection> Collections { get; set; }
public int? StudioID { get; set; }
[LoadableRelation] [EditableRelation] public virtual Studio Studio { get; set; }
[LoadableRelation] [EditableRelation] public virtual ICollection<Genre> Genres { get; set; }
[LoadableRelation] [EditableRelation] public virtual ICollection<PeopleRole> People { get; set; }
[LoadableRelation] public virtual ICollection<Season> Seasons { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }
[LoadableRelation] public virtual ICollection<Library> Libraries { get; set; }
[LoadableRelation] public virtual ICollection<Collection> Collections { get; set; }
public Show() { }

View File

@ -5,11 +5,11 @@ namespace Kyoo.Models
{
public class Studio : IResource
{
[JsonIgnore] public int ID { get; set; }
public int ID { get; set; }
public string Slug { get; set; }
public string Name { get; set; }
[JsonIgnore] public virtual ICollection<Show> Shows { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
public Studio() { }

View File

@ -25,8 +25,8 @@ namespace Kyoo.Models
public string Codec { get; set; }
[MarshalAs(UnmanagedType.I1)] public bool isDefault;
[MarshalAs(UnmanagedType.I1)] public bool isForced;
[JsonIgnore] public string Path { get; set; }
[JsonIgnore] public StreamType Type { get; set; }
[SerializeIgnore] public string Path { get; set; }
[SerializeIgnore] public StreamType Type { get; set; }
public Stream() {}
@ -56,8 +56,8 @@ namespace Kyoo.Models
public class Track : Stream, IResource
{
[JsonIgnore] public int ID { get; set; }
[JsonIgnore] public int EpisodeID { get; set; }
public int ID { get; set; }
public int EpisodeID { get; set; }
public bool IsDefault
{
get => isDefault;
@ -113,8 +113,8 @@ namespace Kyoo.Models
}
}
[JsonIgnore] public bool IsExternal { get; set; }
[JsonIgnore] public virtual Episode Episode { get; set; }
public bool IsExternal { get; set; }
[LoadableRelation] public virtual Episode Episode { get; set; }
public Track() { }

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models.Attributes;
using Kyoo.Models.Watch;
using PathIO = System.IO.Path;
namespace Kyoo.Models
@ -26,7 +25,7 @@ namespace Kyoo.Models
public class WatchItem
{
[JsonIgnore] public readonly int EpisodeID = -1;
public readonly int EpisodeID = -1;
public string ShowTitle;
public string ShowSlug;
@ -35,7 +34,7 @@ namespace Kyoo.Models
public string Title;
public string Slug;
public DateTime? ReleaseDate;
[JsonIgnore] public string Path;
[SerializeIgnore] public string Path;
public Episode PreviousEpisode;
public Episode NextEpisode;
public bool IsMovie;

View File

@ -27,7 +27,6 @@ namespace Kyoo.CommonApi
[HttpGet("{id:int}")]
[Authorize(Policy = "Read")]
[JsonDetailed]
public virtual async Task<ActionResult<T>> Get(int id)
{
T resource = await _repository.Get(id);
@ -39,7 +38,6 @@ namespace Kyoo.CommonApi
[HttpGet("{slug}")]
[Authorize(Policy = "Read")]
[JsonDetailed]
public virtual async Task<ActionResult<T>> Get(string slug)
{
T resource = await _repository.Get(slug);

View File

@ -1,13 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Reflection;
using Kyoo.Models;
using Kyoo.Models.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
@ -19,85 +11,15 @@ namespace Kyoo.Controllers
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = i => member.GetCustomAttribute<JsonReadOnly>(true) == null;
property.ShouldDeserialize = i => member.GetCustomAttribute<JsonIgnore>(true) == null;
// TODO this get called only once and get cached.
// if (member?.GetCustomAttributes<LoadableRelationAttribute>() != null)
// property.NullValueHandling = NullValueHandling.Ignore;
// if (member?.GetCustomAttributes<SerializeIgnoreAttribute>() != null)
// property.ShouldSerialize = _ => false;
// if (member?.GetCustomAttributes<DeserializeIgnoreAttribute>() != null)
// property.ShouldDeserialize = _ => false;
return property;
}
}
public class JsonPropertySelector : JsonPropertyIgnorer
{
private readonly Dictionary<Type, HashSet<string>> _ignored;
private readonly Dictionary<Type, HashSet<string>> _forceSerialize;
public JsonPropertySelector()
{
_ignored = new Dictionary<Type, HashSet<string>>();
_forceSerialize = new Dictionary<Type, HashSet<string>>();
}
public JsonPropertySelector(Dictionary<Type, HashSet<string>> ignored,
Dictionary<Type, HashSet<string>> forceSerialize = null)
{
_ignored = ignored ?? new Dictionary<Type, HashSet<string>>();
_forceSerialize = forceSerialize ?? new Dictionary<Type, HashSet<string>>();
}
private bool IsIgnored(Type type, string propertyName)
{
while (type != null)
{
if (_ignored.ContainsKey(type) && _ignored[type].Contains(propertyName))
return true;
type = type.BaseType;
}
return false;
}
private bool IsSerializationForced(Type type, string propertyName)
{
while (type != null)
{
if (_forceSerialize.ContainsKey(type) && _forceSerialize[type].Contains(propertyName))
return true;
type = type.BaseType;
}
return false;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (IsSerializationForced(property.DeclaringType, property.PropertyName))
property.ShouldSerialize = i => true;
else if (IsIgnored(property.DeclaringType, property.PropertyName))
property.ShouldSerialize = i => false;
return property;
}
}
public class JsonDetailed : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult result)
{
result.Formatters.Add(new NewtonsoftJsonOutputFormatter(
new JsonSerializerSettings
{
ContractResolver = new JsonPropertySelector(null, new Dictionary<Type, HashSet<string>>
{
{typeof(Show), new HashSet<string> {"genres", "studio"}},
{typeof(Episode), new HashSet<string> {"tracks"}},
{typeof(PeopleRole), new HashSet<string> {"show"}}
})
},
context.HttpContext.RequestServices.GetRequiredService<ArrayPool<char>>(),
new MvcOptions()));
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
@ -164,7 +163,7 @@ namespace Kyoo.Controllers
foreach (NavigationEntry navigation in Database.Entry(old).Navigations)
{
if (navigation.Metadata.PropertyInfo.GetCustomAttribute<EditableRelation>() != null)
if (navigation.Metadata.PropertyInfo.GetCustomAttribute<EditableRelationAttribute>() != null)
{
if (resetOld)
{
@ -207,7 +206,7 @@ namespace Kyoo.Controllers
{
if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug.");
if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute<ComposedSlug>() == null)
if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute<ComposedSlugAttribute>() == null)
{
try
{
@ -222,18 +221,6 @@ namespace Kyoo.Controllers
throw new ArgumentException("Resources slug can't be number only.");
}
}
foreach (PropertyInfo property in typeof(T).GetProperties()
.Where(x => typeof(IEnumerable).IsAssignableFrom(x.PropertyType)
&& !typeof(string).IsAssignableFrom(x.PropertyType)
&& x.GetCustomAttribute<EditableRelation>() != null))
{
object value = property.GetValue(resource);
if (value == null || value is ICollection || Utility.IsOfGenericType(value, typeof(ICollection<>)))
continue;
value = Utility.RunGenericMethod<object>(typeof(Enumerable), "ToList", Utility.GetEnumerableType((IEnumerable)value), value);
property.SetValue(resource, value);
}
return Task.CompletedTask;
}

View File

@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models;
using Kyoo.Models.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
@ -20,8 +24,30 @@ namespace Kyoo.CommonApi
where.Remove(key);
}
context.HttpContext.Items["fields"] = context.HttpContext.Request.Query["fields"].ToArray();
// TODO Check if fields are loadable properties of the return type. If not, shorfail the request.
string[] fields = context.HttpContext.Request.Query["fields"].ToArray();
context.HttpContext.Items["fields"] = fields;
if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
{
Type type = descriptor.MethodInfo.ReturnType;
type = Utility.GetGenericDefinition(type, typeof(Task<>))?.GetGenericArguments()[0] ?? type;
type = Utility.GetGenericDefinition(type, typeof(ActionResult<>))?.GetGenericArguments()[0] ?? type;
type = Utility.GetGenericDefinition(type, typeof(Page<>))?.GetGenericArguments()[0] ?? type;
PropertyInfo[] properties = type.GetProperties()
.Where(x => x.GetCustomAttribute<LoadableRelationAttribute>() != null)
.ToArray();
foreach (string field in fields)
{
if (properties.Any(y => string.Equals(y.Name,field, StringComparison.InvariantCultureIgnoreCase)))
continue;
context.Result = new BadRequestObjectResult(new
{
Error = $"{field} does not exist on {type.Name}."
});
return;
}
}
base.OnActionExecuting(context);
}
@ -44,7 +70,7 @@ namespace Kyoo.CommonApi
if (pageType != null)
{
foreach (IResource resource in ((Page<IResource>)result.Value).Items)
foreach (IResource resource in ((dynamic)result.Value).Items)
{
foreach (string field in fields!)
await library!.Load(resource, field);
@ -53,7 +79,7 @@ namespace Kyoo.CommonApi
else if (result.DeclaredType.IsAssignableTo(typeof(IResource)))
{
foreach (string field in fields!)
await library!.Load(result.Value as IResource, field);
await library!.Load((IResource)result.Value, field);
}
}
}

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
{
public class CollectionDE : Collection
{
[JsonIgnore] [NotMergable] public virtual ICollection<CollectionLink> Links { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<CollectionLink> Links { get; set; }
[ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))]
public override ICollection<Show> Shows
{
@ -14,7 +14,7 @@ namespace Kyoo.Models
set => Links = value?.Select(x => new CollectionLink(this, x)).ToList();
}
[JsonIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))]
public override ICollection<Library> Libraries

View File

@ -6,10 +6,10 @@ namespace Kyoo.Models
{
public class GenreDE : Genre
{
[JsonIgnore] [NotMergable] public virtual ICollection<GenreLink> Links { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<GenreLink> Links { get; set; }
[ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))]
[JsonIgnore] [NotMergable] public override ICollection<Show> Shows
[SerializeIgnore] [NotMergable] public override ICollection<Show> Shows
{
get => Links?.Select(x => x.Parent).ToList();
set => Links = value?.Select(x => new GenreLink(x, this)).ToList();

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
{
public class LibraryDE : Library
{
[EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; }
[EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; }
[ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))]
public override ICollection<ProviderID> Providers
{
@ -14,7 +14,7 @@ namespace Kyoo.Models
set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList();
}
[JsonIgnore] [NotMergable] public virtual ICollection<LibraryLink> Links { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<LibraryLink> Links { get; set; }
[ExpressionRewrite(nameof(Links), nameof(LibraryLink.Show))]
public override ICollection<Show> Shows
{

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
{
public class ShowDE : Show
{
[EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))]
public override ICollection<Genre> Genres
{
@ -14,7 +14,7 @@ namespace Kyoo.Models
set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList();
}
[JsonReadOnly] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<LibraryLink> LibraryLinks { get; set; }
[ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))]
public override ICollection<Library> Libraries
{
@ -22,7 +22,7 @@ namespace Kyoo.Models
set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList();
}
[JsonReadOnly] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; }
[SerializeIgnore] [NotMergable] public virtual ICollection<CollectionLink> CollectionLinks { get; set; }
[ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))]
public override ICollection<Collection> Collections
{

View File

@ -7,7 +7,6 @@ using System.Linq;
using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Controllers;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
@ -77,8 +76,6 @@ namespace Kyoo.Api
[FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30)
{
try
{
ICollection<Track> resources = await _libraryManager.GetTracks(
@ -107,8 +104,6 @@ namespace Kyoo.Api
[FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30)
{
try
{
ICollection<Track> resources = await _libraryManager.GetTracks(
@ -139,8 +134,6 @@ namespace Kyoo.Api
[FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30)
{
try
{
ICollection<Track> resources = await _libraryManager.GetTracks(ApiHelper.ParseWhere<Track>(where, x => x.Episode.Show.Slug == showSlug
@ -159,6 +152,21 @@ namespace Kyoo.Api
}
}
[HttpGet("{id:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(int id)
{
string path = (await _libraryManager.GetEpisode(id))?.Path;
if (path == null)
return NotFound();
string thumb = Path.ChangeExtension(path, "jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(string showSlug, int seasonNumber, int episodeNumber)

View File

@ -29,7 +29,6 @@ namespace Kyoo.Api
[HttpGet("{id:int}/role")]
[HttpGet("{id:int}/roles")]
[Authorize(Policy = "Read")]
[JsonDetailed]
public async Task<ActionResult<Page<ShowRole>>> GetRoles(int id,
[FromQuery] string sortBy,
[FromQuery] int afterID,
@ -58,7 +57,6 @@ namespace Kyoo.Api
[HttpGet("{slug}/role")]
[HttpGet("{slug}/roles")]
[Authorize(Policy = "Read")]
[JsonDetailed]
public async Task<ActionResult<Page<ShowRole>>> GetRoles(string slug,
[FromQuery] string sortBy,
[FromQuery] int afterID,
@ -84,12 +82,20 @@ namespace Kyoo.Api
}
}
[HttpGet("{id:int}/poster")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetPeopleIcon(int id)
{
string slug = (await _libraryManager.GetPeople(id)).Slug;
return GetPeopleIcon(slug);
}
[HttpGet("{slug}/poster")]
[Authorize(Policy="Read")]
public IActionResult GetPeopleIcon(string slug)
{
string thumbPath = Path.Combine(_peoplePath, slug + ".jpg");
if (!System.IO.File.Exists(thumbPath))
string thumbPath = Path.GetFullPath(Path.Combine(_peoplePath, slug + ".jpg"));
if (!thumbPath.StartsWith(_peoplePath) || !System.IO.File.Exists(thumbPath))
return NotFound();
return new PhysicalFileResult(Path.GetFullPath(thumbPath), "image/jpg");

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Controllers;
@ -125,5 +126,36 @@ namespace Kyoo.Api
{
return await _libraryManager.GetShow(showID);
}
[HttpGet("{id:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(int id)
{
// TODO remove the next lambda and use a Season.Path (should exit for seasons in a different folder)
string path = (await _libraryManager.GetShow(x => x.Seasons.Any(y => y.ID == id)))?.Path;
int seasonNumber = (await _libraryManager.GetSeason(id)).SeasonNumber;
if (path == null)
return NotFound();
string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
[HttpGet("{showSlug}-s{seasonNumber:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(string showSlug, int seasonNumber)
{
// TODO use a season.Path
string path = (await _libraryManager.GetShow(showSlug))?.Path;
if (path == null)
return NotFound();
string thumb = Path.Combine(path, $"season-{seasonNumber}.jpg");
if (System.IO.File.Exists(thumb))
return new PhysicalFileResult(Path.GetFullPath(thumb), "image/jpg");
return NotFound();
}
}
}