diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index f3f0910a..e6972d04 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -69,10 +69,10 @@ namespace Kyoo.Controllers where T : class, IResource where T2 : class, new(); - Task Load(T obj, string memberName) + Task Load([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> GetItemsFromLibrary(int id, diff --git a/Kyoo.Common/Models/Attributes/ComposedSlug.cs b/Kyoo.Common/Models/Attributes/ComposedSlug.cs index a66fef71..4595d5a4 100644 --- a/Kyoo.Common/Models/Attributes/ComposedSlug.cs +++ b/Kyoo.Common/Models/Attributes/ComposedSlug.cs @@ -3,5 +3,5 @@ using System; namespace Kyoo.Models.Attributes { [AttributeUsage(AttributeTargets.Class)] - public class ComposedSlug : Attribute { } + public class ComposedSlugAttribute : Attribute { } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/EditableRelation.cs b/Kyoo.Common/Models/Attributes/EditableRelation.cs deleted file mode 100644 index 4275dca4..00000000 --- a/Kyoo.Common/Models/Attributes/EditableRelation.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Kyoo.Models.Attributes -{ - [AttributeUsage(AttributeTargets.Property, Inherited = false)] - public class EditableRelation : Attribute { } -} \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/MergeAttributes.cs b/Kyoo.Common/Models/Attributes/MergeAttributes.cs index 1944c89b..399f5389 100644 --- a/Kyoo.Common/Models/Attributes/MergeAttributes.cs +++ b/Kyoo.Common/Models/Attributes/MergeAttributes.cs @@ -8,7 +8,4 @@ namespace Kyoo.Models.Attributes { void OnMerge(object merged); } - - public class JsonReadOnly : Attribute { } - public class JsonIgnore : JsonReadOnly { } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs new file mode 100644 index 00000000..36edbbf2 --- /dev/null +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -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 { } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs new file mode 100644 index 00000000..3622f77d --- /dev/null +++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs @@ -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 {} +} \ No newline at end of file diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index 945e578b..71e1946a 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -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; } diff --git a/Kyoo.Common/Models/PeopleRole.cs b/Kyoo.Common/Models/PeopleRole.cs index a9fc0582..fbde0d5e 100644 --- a/Kyoo.Common/Models/PeopleRole.cs +++ b/Kyoo.Common/Models/PeopleRole.cs @@ -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 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; } diff --git a/Kyoo.Common/Models/Resources/Collection.cs b/Kyoo.Common/Models/Resources/Collection.cs index 283fe017..34a41f7a 100644 --- a/Kyoo.Common/Models/Resources/Collection.cs +++ b/Kyoo.Common/Models/Resources/Collection.cs @@ -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 Shows { get; set; } - [JsonIgnore] public virtual ICollection Libraries { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Libraries { get; set; } public Collection() { } diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 7a8007ee..9ef8c684 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -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 ExternalIDs { get; set; } + [SerializeIgnore] public string Poster { get; set; } + [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public virtual ICollection Tracks { get; set; } + [LoadableRelation] public virtual ICollection 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() { } diff --git a/Kyoo.Common/Models/Resources/Genre.cs b/Kyoo.Common/Models/Resources/Genre.cs index 16e8640d..26fbe5a4 100644 --- a/Kyoo.Common/Models/Resources/Genre.cs +++ b/Kyoo.Common/Models/Resources/Genre.cs @@ -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 Shows { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } public Genre() {} diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index 59e5b01d..1556753b 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -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 Providers { get; set; } - [JsonIgnore] public virtual ICollection Shows { get; set; } - [JsonIgnore] public virtual ICollection Collections { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } + [LoadableRelation] public virtual ICollection Collections { get; set; } public Library() { } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index fe9cc4a1..ca1aec5d 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -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 ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection Roles { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection Roles { get; set; } public People() {} diff --git a/Kyoo.Common/Models/Resources/ProviderID.cs b/Kyoo.Common/Models/Resources/ProviderID.cs index 49e19774..411bf976 100644 --- a/Kyoo.Common/Models/Resources/ProviderID.cs +++ b/Kyoo.Common/Models/Resources/ProviderID.cs @@ -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; } diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index 0aee3e9a..ecaff2e4 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -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 ExternalIDs { get; set; } + [SerializeIgnore] public string Poster { get; set; } + public string Thumb => $"/api/seasons/{Slug}/thumb"; + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public virtual Show Show { get; set; } - [JsonIgnore] public virtual ICollection Episodes { get; set; } + [LoadableRelation] public virtual Show Show { get; set; } + [LoadableRelation] public virtual ICollection Episodes { get; set; } public Season() { } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index fadac0bd..c4feb24c 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -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 ExternalIDs { get; set; } + [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - [JsonIgnore] public int? StudioID { get; set; } - [EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection Genres { get; set; } - [EditableRelation] [JsonReadOnly] public virtual ICollection People { get; set; } - [JsonIgnore] public virtual ICollection Seasons { get; set; } - [JsonIgnore] public virtual ICollection Episodes { get; set; } - [JsonIgnore] public virtual ICollection Libraries { get; set; } - [JsonIgnore] public virtual ICollection Collections { get; set; } + public int? StudioID { get; set; } + [LoadableRelation] [EditableRelation] public virtual Studio Studio { get; set; } + [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; } + [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; } + [LoadableRelation] public virtual ICollection Seasons { get; set; } + [LoadableRelation] public virtual ICollection Episodes { get; set; } + [LoadableRelation] public virtual ICollection Libraries { get; set; } + [LoadableRelation] public virtual ICollection Collections { get; set; } public Show() { } diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 07959b01..9eea3a7b 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -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 Shows { get; set; } + [LoadableRelation] public virtual ICollection Shows { get; set; } public Studio() { } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 86e4bdbb..f4b82fc7 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -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() { } diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index a86f5490..e007e04c 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -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; diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index b5270c4d..4d674cc2 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -27,7 +27,6 @@ namespace Kyoo.CommonApi [HttpGet("{id:int}")] [Authorize(Policy = "Read")] - [JsonDetailed] public virtual async Task> 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> Get(string slug) { T resource = await _repository.Get(slug); diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 6cbc9c89..78f49ed8 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -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(true) == null; - property.ShouldDeserialize = i => member.GetCustomAttribute(true) == null; + // TODO this get called only once and get cached. + + // if (member?.GetCustomAttributes() != null) + // property.NullValueHandling = NullValueHandling.Ignore; + // if (member?.GetCustomAttributes() != null) + // property.ShouldSerialize = _ => false; + // if (member?.GetCustomAttributes() != null) + // property.ShouldDeserialize = _ => false; return property; } } - - public class JsonPropertySelector : JsonPropertyIgnorer - { - private readonly Dictionary> _ignored; - private readonly Dictionary> _forceSerialize; - - public JsonPropertySelector() - { - _ignored = new Dictionary>(); - _forceSerialize = new Dictionary>(); - } - - public JsonPropertySelector(Dictionary> ignored, - Dictionary> forceSerialize = null) - { - _ignored = ignored ?? new Dictionary>(); - _forceSerialize = forceSerialize ?? new Dictionary>(); - } - - 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> - { - {typeof(Show), new HashSet {"genres", "studio"}}, - {typeof(Episode), new HashSet {"tracks"}}, - {typeof(PeopleRole), new HashSet {"show"}} - }) - }, - context.HttpContext.RequestServices.GetRequiredService>(), - new MvcOptions())); - } - } - } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index d4197e08..3f2b4cf6 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -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() != null) + if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != 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() == null) + if (int.TryParse(resource.Slug, out int _) && typeof(T).GetCustomAttribute() == 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() != null)) - { - object value = property.GetValue(resource); - if (value == null || value is ICollection || Utility.IsOfGenericType(value, typeof(ICollection<>))) - continue; - value = Utility.RunGenericMethod(typeof(Enumerable), "ToList", Utility.GetEnumerableType((IEnumerable)value), value); - property.SetValue(resource, value); - } return Task.CompletedTask; } diff --git a/Kyoo.CommonAPI/ResourceViewAttribute.cs b/Kyoo.CommonAPI/ResourceViewAttribute.cs index a30683db..9a2dab33 100644 --- a/Kyoo.CommonAPI/ResourceViewAttribute.cs +++ b/Kyoo.CommonAPI/ResourceViewAttribute.cs @@ -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() != 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)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); } } } diff --git a/Kyoo/Models/Resources/CollectionDE.cs b/Kyoo/Models/Resources/CollectionDE.cs index 622e602e..10a53852 100644 --- a/Kyoo/Models/Resources/CollectionDE.cs +++ b/Kyoo/Models/Resources/CollectionDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class CollectionDE : Collection { - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(CollectionLink.Child))] public override ICollection Shows { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => Links = value?.Select(x => new CollectionLink(this, x)).ToList(); } - [JsonIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(GenreLink.Child))] public override ICollection Libraries diff --git a/Kyoo/Models/Resources/GenreDE.cs b/Kyoo/Models/Resources/GenreDE.cs index 892be000..0a77198e 100644 --- a/Kyoo/Models/Resources/GenreDE.cs +++ b/Kyoo/Models/Resources/GenreDE.cs @@ -6,10 +6,10 @@ namespace Kyoo.Models { public class GenreDE : Genre { - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(GenreLink.Child))] - [JsonIgnore] [NotMergable] public override ICollection Shows + [SerializeIgnore] [NotMergable] public override ICollection Shows { get => Links?.Select(x => x.Parent).ToList(); set => Links = value?.Select(x => new GenreLink(x, this)).ToList(); diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs index 32992c6c..47c94c18 100644 --- a/Kyoo/Models/Resources/LibraryDE.cs +++ b/Kyoo/Models/Resources/LibraryDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class LibraryDE : Library { - [EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } + [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Child))] public override ICollection Providers { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => ProviderLinks = value?.Select(x => new ProviderLink(x, this)).ToList(); } - [JsonIgnore] [NotMergable] public virtual ICollection Links { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection Links { get; set; } [ExpressionRewrite(nameof(Links), nameof(LibraryLink.Show))] public override ICollection Shows { diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs index e1928214..7491effe 100644 --- a/Kyoo/Models/Resources/ShowDE.cs +++ b/Kyoo/Models/Resources/ShowDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class ShowDE : Show { - [EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection GenreLinks { get; set; } + [EditableRelation] [SerializeIgnore] [NotMergable] public virtual ICollection GenreLinks { get; set; } [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Child))] public override ICollection Genres { @@ -14,7 +14,7 @@ namespace Kyoo.Models set => GenreLinks = value?.Select(x => new GenreLink(this, x)).ToList(); } - [JsonReadOnly] [NotMergable] public virtual ICollection LibraryLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection LibraryLinks { get; set; } [ExpressionRewrite(nameof(LibraryLinks), nameof(LibraryLink.Library))] public override ICollection Libraries { @@ -22,7 +22,7 @@ namespace Kyoo.Models set => LibraryLinks = value?.Select(x => new LibraryLink(x, this)).ToList(); } - [JsonReadOnly] [NotMergable] public virtual ICollection CollectionLinks { get; set; } + [SerializeIgnore] [NotMergable] public virtual ICollection CollectionLinks { get; set; } [ExpressionRewrite(nameof(CollectionLinks), nameof(CollectionLink.Parent))] public override ICollection Collections { diff --git a/Kyoo/Views/API/EpisodeApi.cs b/Kyoo/Views/API/EpisodeApi.cs index d01468b2..de9ae424 100644 --- a/Kyoo/Views/API/EpisodeApi.cs +++ b/Kyoo/Views/API/EpisodeApi.cs @@ -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 where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks( @@ -107,8 +104,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks( @@ -139,8 +134,6 @@ namespace Kyoo.Api [FromQuery] Dictionary where, [FromQuery] int limit = 30) { - - try { ICollection resources = await _libraryManager.GetTracks(ApiHelper.ParseWhere(where, x => x.Episode.Show.Slug == showSlug @@ -159,6 +152,21 @@ namespace Kyoo.Api } } + [HttpGet("{id:int}/thumb")] + [Authorize(Policy="Read")] + public async Task 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 GetThumb(string showSlug, int seasonNumber, int episodeNumber) diff --git a/Kyoo/Views/API/PeopleApi.cs b/Kyoo/Views/API/PeopleApi.cs index 96d73490..a264363c 100644 --- a/Kyoo/Views/API/PeopleApi.cs +++ b/Kyoo/Views/API/PeopleApi.cs @@ -29,7 +29,6 @@ namespace Kyoo.Api [HttpGet("{id:int}/role")] [HttpGet("{id:int}/roles")] [Authorize(Policy = "Read")] - [JsonDetailed] public async Task>> 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>> 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 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"); diff --git a/Kyoo/Views/API/SeasonApi.cs b/Kyoo/Views/API/SeasonApi.cs index f6650617..4bedf5ec 100644 --- a/Kyoo/Views/API/SeasonApi.cs +++ b/Kyoo/Views/API/SeasonApi.cs @@ -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 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 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(); + } } } \ No newline at end of file